diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3aaec2b --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Application settings +TZ=Europe/Moscow +APP_PORT=8044 + +MONGO_INITDB_ROOT_USERNAME=qqq +MONGO_INITDB_ROOT_PASSWORD=qqq + +# MongoDB connection string +MONGO_ADDR=mongodb://qqq:qqq@127.0.0.1:27018 diff --git a/.gitea/workflows/check.yaml b/.gitea/workflows/check.yaml new file mode 100644 index 0000000..25fcc22 --- /dev/null +++ b/.gitea/workflows/check.yaml @@ -0,0 +1,28 @@ +name: Code Quality Checks +run-name: Проверка кода (lint & typecheck) от ${{ gitea.actor }} +on: [push] + +jobs: + lint-and-typecheck: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run eslint -- --quiet + + - name: Run TypeScript type check + run: npx tsc --noEmit + + - name: Run tests + run: npm test -- --quiet \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d37b6fc..577a78b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,38 @@ -FROM node:20 +FROM node:22 AS builder + +WORKDIR /usr/src/app/ + +# Сначала копируем только файлы, необходимые для установки зависимостей +COPY ./package.json /usr/src/app/package.json +COPY ./package-lock.json /usr/src/app/package-lock.json + +# Устанавливаем все зависимости +RUN npm ci + +# Затем копируем исходный код проекта и файлы конфигурации +COPY ./tsconfig.json /usr/src/app/tsconfig.json +COPY ./server /usr/src/app/server + +# Сборка проекта +RUN npm run build + +# Вторая стадия - рабочий образ +FROM node:22 RUN mkdir -p /usr/src/app/server/log/ WORKDIR /usr/src/app/ -COPY ./server /usr/src/app/server +# Копирование только package.json/package-lock.json для продакшн зависимостей COPY ./package.json /usr/src/app/package.json COPY ./package-lock.json /usr/src/app/package-lock.json -COPY ./.serverrc.js /usr/src/app/.serverrc.js -# COPY ./.env /usr/src/app/.env -# RUN npm i --omit=dev -RUN npm ci +# Установка только продакшн зависимостей +RUN npm ci --production + +# Копирование собранного приложения из билдера +COPY --from=builder /usr/src/app/dist /usr/src/app/dist +COPY --from=builder /usr/src/app/server /usr/src/app/server + EXPOSE 8044 CMD ["npm", "run", "up:prod"] diff --git a/d-scripts/rerun.sh b/d-scripts/rerun.sh index 03dbb37..d6ee428 100644 --- a/d-scripts/rerun.sh +++ b/d-scripts/rerun.sh @@ -1,6 +1,12 @@ #!/bin/sh docker stop ms-mongo -docker volume remove ms_volume -docker volume create ms_volume -docker run --rm -v ms_volume:/data/db --name ms-mongo -p 27017:27017 -d mongo:8.0.3 +docker volume remove ms_volume8 +docker volume create ms_volume8 +docker run --rm \ + -v ms_volume8:/data/db \ + --name ms-mongo \ + -p 27018:27017 \ + -e MONGO_INITDB_ROOT_USERNAME=qqq \ + -e MONGO_INITDB_ROOT_PASSWORD=qqq \ + -d mongo:8.0.3 diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 76b4b32..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,25 +0,0 @@ -version: "3" - -volumes: - ms_volume8: - ms_logs: - -services: - mongoDb: - image: mongo:8.0.3 - volumes: - - ms_volume8:/data/db - restart: always - # ports: - # - 27017:27017 - multy-stubs: - # build: . - image: bro.js/ms/bh:$TAG - restart: always - volumes: - - ms_logs:/usr/src/app/server/log - ports: - - 8044:8044 - environment: - - TZ=Europe/Moscow - - MONGO_ADDR=mongodb \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..17fefd4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3" + +volumes: + ms_volume8: + ms_logs: + +services: + multy-stubs: + image: bro.js/ms/bh:$TAG + restart: always + volumes: + - ms_logs:/usr/src/app/server/log + ports: + - 8044:8044 + environment: + - TZ=Europe/Moscow + - MONGO_ADDR=${MONGO_ADDR} + # depends_on: + # mongoDb: + # condition: service_started + # mongoDb: + # image: mongo:8.0.3 + # volumes: + # - ms_volume8:/data/db + # restart: always + # environment: + # - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} + # - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} + # ports: + # - 27018:27017 \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 21a1f6d..bcdabf1 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,7 +4,7 @@ import pluginJs from "@eslint/js"; export default [ { ignores: ['server/routers/old/*'] }, - { files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } }, + { files: ["**/*.js"], languageOptions: { } }, { languageOptions: { globals: globals.node } }, pluginJs.configs.recommended, { diff --git a/jest.config.js b/jest.config.js index c53c59a..e1d5760 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,43 +1,43 @@ /** - * For a detailed explanation regarding each configuration property, visit: + * Для подробного объяснения каждого свойства конфигурации, посетите: * https://jestjs.io/docs/configuration */ /** @type {import('jest').Config} */ const config = { - // All imported modules in your tests should be mocked automatically + // Все импортированные модули в тестах должны быть автоматически замоканы // automock: false, - // Stop running tests after `n` failures + // Остановить выполнение тестов после `n` неудач // bail: 0, - // The directory where Jest should store its cached dependency information + // Директория, где Jest должен хранить кэшированную информацию о зависимостях // cacheDirectory: "C:\\Users\\alex\\AppData\\Local\\Temp\\jest", - // Automatically clear mock calls, instances, contexts and results before every test + // Автоматически очищать вызовы моков, экземпляры, контексты и результаты перед каждым тестом clearMocks: true, - // Indicates whether the coverage information should be collected while executing the test + // Указывает, должна ли собираться информация о покрытии во время выполнения тестов collectCoverage: true, - // An array of glob patterns indicating a set of files for which coverage information should be collected + // Массив glob-паттернов, указывающих набор файлов, для которых должна собираться информация о покрытии collectCoverageFrom: [ "/server/routers/**/*.js" ], - // The directory where Jest should output its coverage files + // Директория, куда Jest должен выводить файлы покрытия coverageDirectory: "coverage", - // An array of regexp pattern strings used to skip coverage collection + // Массив строк regexp-паттернов, используемых для пропуска сбора покрытия coveragePathIgnorePatterns: [ "\\\\node_modules\\\\", "/server/routers/old" ], - // Indicates which provider should be used to instrument code for coverage + // Указывает, какой провайдер должен использоваться для инструментирования кода для покрытия coverageProvider: "v8", - // A list of reporter names that Jest uses when writing coverage reports + // Список имен репортеров, которые Jest использует при записи отчетов о покрытии // coverageReporters: [ // "json", // "text", @@ -45,156 +45,159 @@ const config = { // "clover" // ], - // An object that configures minimum threshold enforcement for coverage results + // Объект, который настраивает принудительное применение минимальных порогов для результатов покрытия // coverageThreshold: undefined, - // A path to a custom dependency extractor + // Путь к пользовательскому извлекателю зависимостей // dependencyExtractor: undefined, - // Make calling deprecated APIs throw helpful error messages + // Заставить вызовы устаревших API выбрасывать полезные сообщения об ошибках // errorOnDeprecated: false, - // The default configuration for fake timers + // Конфигурация по умолчанию для поддельных таймеров // fakeTimers: { // "enableGlobally": false // }, - // Force coverage collection from ignored files using an array of glob patterns + // Принудительно собирать покрытие из игнорируемых файлов, используя массив glob-паттернов // forceCoverageMatch: [], - // A path to a module which exports an async function that is triggered once before all test suites + // Путь к модулю, который экспортирует асинхронную функцию, вызываемую один раз перед всеми наборами тестов // globalSetup: undefined, - // A path to a module which exports an async function that is triggered once after all test suites + // Путь к модулю, который экспортирует асинхронную функцию, вызываемую один раз после всех наборов тестов // globalTeardown: undefined, - // A set of global variables that need to be available in all test environments + // Набор глобальных переменных, которые должны быть доступны во всех тестовых окружениях // globals: {}, - // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // Максимальное количество воркеров, используемых для запуска тестов. Может быть указано в % или числом. Например, maxWorkers: 10% будет использовать 10% от количества CPU + 1 в качестве максимального числа воркеров. maxWorkers: 2 будет использовать максимум 2 воркера. // maxWorkers: "50%", - // An array of directory names to be searched recursively up from the requiring module's location + // Массив имен директорий, которые должны быть рекурсивно найдены вверх от местоположения требуемого модуля // moduleDirectories: [ // "node_modules" // ], - // An array of file extensions your modules use - // moduleFileExtensions: [ - // "js", - // "mjs", - // "cjs", - // "jsx", - // "ts", - // "tsx", - // "json", - // "node" - // ], + // Массив расширений файлов, которые используют ваши модули + moduleFileExtensions: [ + "js", + "mjs", + "cjs", + "jsx", + "ts", + "tsx", + "json", + "node" + ], - // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // Карта из регулярных выражений в имена модулей или массивы имен модулей, которые позволяют заглушить ресурсы одним модулем // moduleNameMapper: {}, - // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // Массив строк regexp-паттернов, сопоставляемых со всеми путями модулей перед тем, как они будут считаться 'видимыми' для загрузчика модулей // modulePathIgnorePatterns: [], - // Activates notifications for test results + // Активирует уведомления для результатов тестов // notify: false, - // An enum that specifies notification mode. Requires { notify: true } + // Перечисление, которое указывает режим уведомлений. Требует { notify: true } // notifyMode: "failure-change", - // A preset that is used as a base for Jest's configuration - // preset: undefined, + // Пресет, который используется в качестве основы для конфигурации Jest + preset: 'ts-jest', - // Run tests from one or more projects + // Запускать тесты из одного или нескольких проектов // projects: undefined, - // Use this configuration option to add custom reporters to Jest + // Используйте эту опцию конфигурации для добавления пользовательских репортеров в Jest // reporters: undefined, - // Automatically reset mock state before every test + // Автоматически сбрасывать состояние моков перед каждым тестом // resetMocks: false, - // Reset the module registry before running each individual test + // Сбрасывать реестр модулей перед запуском каждого отдельного теста // resetModules: false, - // A path to a custom resolver + // Путь к пользовательскому резолверу // resolver: undefined, - // Automatically restore mock state and implementation before every test + // Автоматически восстанавливать состояние моков и реализацию перед каждым тестом // restoreMocks: false, - // The root directory that Jest should scan for tests and modules within + // Корневая директория, которую Jest должен сканировать для поиска тестов и модулей // rootDir: undefined, - // A list of paths to directories that Jest should use to search for files in + // Список путей к директориям, которые Jest должен использовать для поиска файлов // roots: [ // "" // ], - // Allows you to use a custom runner instead of Jest's default test runner + // Позволяет использовать пользовательский раннер вместо стандартного тестового раннера Jest // runner: "jest-runner", - // The paths to modules that run some code to configure or set up the testing environment before each test + // Пути к модулям, которые выполняют некоторый код для настройки или подготовки тестового окружения перед каждым тестом // setupFiles: [], - // A list of paths to modules that run some code to configure or set up the testing framework before each test + // Список путей к модулям, которые выполняют некоторый код для настройки или подготовки тестового фреймворка перед каждым тестом // setupFilesAfterEnv: [], - // The number of seconds after which a test is considered as slow and reported as such in the results. + // Количество секунд, после которого тест считается медленным и сообщается как таковой в результатах. // slowTestThreshold: 5, - // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // Список путей к модулям сериализаторов снимков, которые Jest должен использовать для тестирования снимков // snapshotSerializers: [], - // The test environment that will be used for testing - // testEnvironment: "jest-environment-node", + // Тестовое окружение, которое будет использоваться для тестирования + testEnvironment: "node", - // Options that will be passed to the testEnvironment + // Опции, которые будут переданы в testEnvironment // testEnvironmentOptions: {}, - // Adds a location field to test results + // Добавляет поле местоположения к результатам тестов // testLocationInResults: false, - // The glob patterns Jest uses to detect test files - // testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - // "**/?(*.)+(spec|test).[tj]s?(x)" - // ], + // Glob-паттерны, которые Jest использует для обнаружения тестовых файлов + testMatch: [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[tj]s?(x)" + ], - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // Массив строк regexp-паттернов, которые сопоставляются со всеми тестовыми путями, сопоставленные тесты пропускаются // testPathIgnorePatterns: [ // "\\\\node_modules\\\\" // ], - // The regexp pattern or array of patterns that Jest uses to detect test files + // Regexp-паттерн или массив паттернов, которые Jest использует для обнаружения тестовых файлов // testRegex: [], - // This option allows the use of a custom results processor + // Эта опция позволяет использовать пользовательский процессор результатов // testResultsProcessor: undefined, - // This option allows use of a custom test runner + // Эта опция позволяет использовать пользовательский тестовый раннер // testRunner: "jest-circus/runner", - // A map from regular expressions to paths to transformers - // transform: undefined, + // Карта из регулярных выражений в пути к трансформерам + transform: { + '^.+\\.ts$': 'ts-jest', + '^.+\\.tsx$': 'ts-jest', + }, - // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // Массив строк regexp-паттернов, которые сопоставляются со всеми путями исходных файлов, сопоставленные файлы будут пропускать трансформацию // transformIgnorePatterns: [ // "\\\\node_modules\\\\", // "\\.pnp\\.[^\\\\]+$" // ], - // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // Массив строк regexp-паттернов, которые сопоставляются со всеми модулями перед тем, как загрузчик модулей автоматически вернет мок для них // unmockedModulePathPatterns: undefined, - // Indicates whether each individual test should be reported during the run + // Указывает, должен ли каждый отдельный тест сообщаться во время выполнения verbose: true, - // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // Массив regexp-паттернов, которые сопоставляются со всеми путями исходных файлов перед повторным запуском тестов в режиме наблюдения // watchPathIgnorePatterns: [], - // Whether to use watchman for file crawling + // Использовать ли watchman для обхода файлов // watchman: true, }; diff --git a/package-lock.json b/package-lock.json index e200158..64ef6f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,21 @@ { "name": "multi-stub", - "version": "1.2.1", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-stub", - "version": "1.2.1", + "version": "2.0.0", "license": "MIT", "dependencies": { + "@langchain/community": "^0.3.56", + "@langchain/core": "^0.3.77", + "@langchain/langgraph": "^0.4.9", "ai": "^4.1.13", "axios": "^1.7.7", "bcrypt": "^5.1.0", + "bcryptjs": "^3.0.3", "body-parser": "^1.19.0", "cookie-parser": "^1.4.5", "cors": "^2.8.5", @@ -21,27 +25,34 @@ "express": "5.0.1", "express-jwt": "^8.5.1", "express-session": "^1.18.1", + "gigachat": "^0.0.16", "jsdom": "^25.0.1", "jsonwebtoken": "^9.0.2", - "mongodb": "^6.12.0", - "mongoose": "^8.9.2", + "langchain": "^0.3.34", + "langchain-gigachat": "^0.0.14", + "mongodb": "^6.20.0", + "mongoose": "^8.18.2", "mongoose-sequence": "^6.0.1", - "morgan": "^1.10.0", + "morgan": "^1.10.1", "multer": "^1.4.5-lts.1", "pbkdf2-password": "^1.2.1", "rotating-file-stream": "^3.2.5", "socket.io": "^4.8.1", - "uuid": "^11.0.3" + "zod": "^3.24.3" }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/jest": "^30.0.0", "@types/node": "22.10.2", "eslint": "^9.17.0", "globals": "^15.14.0", "jest": "^29.7.0", "mockingoose": "^2.16.2", "nodemon": "3.1.9", - "supertest": "^7.0.0" + "supertest": "^7.0.0", + "ts-jest": "^29.4.6", + "ts-node-dev": "2.0.0", + "typescript": "5.7.3" } }, "node_modules/@ai-sdk/provider": { @@ -142,6 +153,60 @@ "node": ">=6.0.0" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", + "integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==", + "license": "MIT", + "peer": true, + "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/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.111", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.111.tgz", + "integrity": "sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/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", + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@anthropic-ai/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", + "peer": true + }, "node_modules/@asamuzakjp/css-color": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.3.tgz", @@ -162,14 +227,15 @@ "license": "ISC" }, "node_modules/@babel/code-frame": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", - "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "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": { - "@babel/highlight": "^7.25.7", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -379,9 +445,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", - "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "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": { @@ -412,100 +478,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", - "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/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" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/parser": { "version": "7.25.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", @@ -852,6 +824,110 @@ "dev": true, "license": "MIT" }, + "node_modules/@browserbasehq/sdk": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.6.0.tgz", + "integrity": "sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==", + "license": "Apache-2.0", + "peer": true, + "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/@browserbasehq/sdk/node_modules/@types/node": { + "version": "18.19.111", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.111.tgz", + "integrity": "sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@browserbasehq/sdk/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", + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@browserbasehq/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", + "peer": true + }, + "node_modules/@browserbasehq/stagehand": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@browserbasehq/stagehand/-/stagehand-1.14.0.tgz", + "integrity": "sha512-Hi/EzgMFWz+FKyepxHTrqfTPjpsuBS4zRy3e9sbMpBgLPv+9c0R+YZEvS7Bw4mTS66QtvvURRT6zgDGFotthVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@anthropic-ai/sdk": "^0.27.3", + "@browserbasehq/sdk": "^2.0.0", + "ws": "^8.18.0", + "zod-to-json-schema": "^3.23.5" + }, + "peerDependencies": { + "@playwright/test": "^1.42.1", + "deepmerge": "^4.3.1", + "dotenv": "^16.4.5", + "openai": "^4.62.1", + "zod": "^3.23.8" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT" + }, + "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": { + "@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, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", @@ -1150,6 +1226,46 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "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", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1216,6 +1332,38 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@ibm-cloud/watsonx-ai": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.6.7.tgz", + "integrity": "sha512-lyHG5pjIINc+3fVbodD+ui0kvs7xk6TRAPJasK+8d8+j/FXS6TNsNGjvP79nfQJPTTYYy9IXxFc/3Z4jAqfD7w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "^18.0.0", + "extend": "3.0.2", + "ibm-cloud-sdk-core": "^5.3.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@ibm-cloud/watsonx-ai/node_modules/@types/node": { + "version": "18.19.111", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.111.tgz", + "integrity": "sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@ibm-cloud/watsonx-ai/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", + "peer": 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", @@ -1399,6 +1547,16 @@ } } }, + "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": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -1460,6 +1618,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.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, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/globals": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", @@ -1476,6 +1644,30 @@ "node": "^14.15.0 || ^16.10.0 || >=18.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, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern/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": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/reporters": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", @@ -1678,6 +1870,786 @@ "@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/@langchain/community": { + "version": "0.3.56", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.56.tgz", + "integrity": "sha512-lDjUnRfHAX7aMXyEB2EWbe5qOmdQdz8n+0CNQ4ExpLy3NOFQhEVkWclhsucaX04zh0r/VH5Pkk9djpnhPBDH7g==", + "license": "MIT", + "dependencies": { + "@langchain/openai": ">=0.2.0 <0.7.0", + "@langchain/weaviate": "^0.2.0", + "binary-extensions": "^2.2.0", + "expr-eval": "^2.0.2", + "flat": "^5.0.2", + "js-yaml": "^4.1.0", + "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", + "langsmith": "^0.3.67", + "uuid": "^10.0.0", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@arcjet/redact": "^v1.0.0-alpha.23", + "@aws-crypto/sha256-js": "^5.0.0", + "@aws-sdk/client-bedrock-agent-runtime": "^3.749.0", + "@aws-sdk/client-bedrock-runtime": "^3.749.0", + "@aws-sdk/client-dynamodb": "^3.749.0", + "@aws-sdk/client-kendra": "^3.749.0", + "@aws-sdk/client-lambda": "^3.749.0", + "@aws-sdk/client-s3": "^3.749.0", + "@aws-sdk/client-sagemaker-runtime": "^3.749.0", + "@aws-sdk/client-sfn": "^3.749.0", + "@aws-sdk/credential-provider-node": "^3.388.0", + "@azure/search-documents": "^12.0.0", + "@azure/storage-blob": "^12.15.0", + "@browserbasehq/sdk": "*", + "@browserbasehq/stagehand": "^1.0.0", + "@clickhouse/client": "^0.2.5", + "@cloudflare/ai": "*", + "@datastax/astra-db-ts": "^1.0.0", + "@elastic/elasticsearch": "^8.4.0", + "@getmetal/metal-sdk": "*", + "@getzep/zep-cloud": "^1.0.6", + "@getzep/zep-js": "^0.9.0", + "@gomomento/sdk": "^1.51.1", + "@gomomento/sdk-core": "^1.51.1", + "@google-ai/generativelanguage": "*", + "@google-cloud/storage": "^6.10.1 || ^7.7.0", + "@gradientai/nodejs-sdk": "^1.2.0", + "@huggingface/inference": "^4.0.5", + "@huggingface/transformers": "^3.5.2", + "@ibm-cloud/watsonx-ai": "*", + "@lancedb/lancedb": "^0.19.1", + "@langchain/core": ">=0.3.58 <0.4.0", + "@layerup/layerup-security": "^1.5.12", + "@libsql/client": "^0.14.0", + "@mendable/firecrawl-js": "^1.4.3", + "@mlc-ai/web-llm": "*", + "@mozilla/readability": "*", + "@neondatabase/serverless": "*", + "@notionhq/client": "^2.2.10", + "@opensearch-project/opensearch": "*", + "@pinecone-database/pinecone": "*", + "@planetscale/database": "^1.8.0", + "@premai/prem-sdk": "^0.3.25", + "@qdrant/js-client-rest": "^1.15.0", + "@raycast/api": "^1.55.2", + "@rockset/client": "^0.9.1", + "@smithy/eventstream-codec": "^2.0.5", + "@smithy/protocol-http": "^3.0.6", + "@smithy/signature-v4": "^2.0.10", + "@smithy/util-utf8": "^2.0.0", + "@spider-cloud/spider-client": "^0.0.21", + "@supabase/supabase-js": "^2.45.0", + "@tensorflow-models/universal-sentence-encoder": "*", + "@tensorflow/tfjs-converter": "*", + "@tensorflow/tfjs-core": "*", + "@upstash/ratelimit": "^1.1.3 || ^2.0.3", + "@upstash/redis": "^1.20.6", + "@upstash/vector": "^1.1.1", + "@vercel/kv": "*", + "@vercel/postgres": "*", + "@writerai/writer-sdk": "^0.40.2", + "@xata.io/client": "^0.28.0", + "@zilliz/milvus2-sdk-node": ">=2.3.5", + "apify-client": "^2.7.1", + "assemblyai": "^4.6.0", + "azion": "^1.11.1", + "better-sqlite3": ">=9.4.0 <12.0.0", + "cassandra-driver": "^4.7.2", + "cborg": "^4.1.1", + "cheerio": "^1.0.0-rc.12", + "chromadb": "*", + "closevector-common": "0.1.3", + "closevector-node": "0.1.6", + "closevector-web": "0.1.6", + "cohere-ai": "*", + "convex": "^1.3.1", + "crypto-js": "^4.2.0", + "d3-dsv": "^2.0.0", + "discord.js": "^14.14.1", + "duck-duck-scrape": "^2.2.5", + "epub2": "^3.0.1", + "fast-xml-parser": "*", + "firebase-admin": "^11.9.0 || ^12.0.0 || ^13.0.0", + "google-auth-library": "*", + "googleapis": "*", + "hnswlib-node": "^3.0.0", + "html-to-text": "^9.0.5", + "ibm-cloud-sdk-core": "*", + "ignore": "^5.2.0", + "interface-datastore": "^8.2.11", + "ioredis": "^5.3.2", + "it-all": "^3.0.4", + "jsdom": "*", + "jsonwebtoken": "^9.0.2", + "llmonitor": "^0.5.9", + "lodash": "^4.17.21", + "lunary": "^0.7.10", + "mammoth": "^1.6.0", + "mariadb": "^3.4.0", + "mem0ai": "^2.1.8", + "mongodb": "^6.17.0", + "mysql2": "^3.9.8", + "neo4j-driver": "*", + "notion-to-md": "^3.1.0", + "officeparser": "^4.0.4", + "openai": "*", + "pdf-parse": "1.1.1", + "pg": "^8.11.0", + "pg-copy-streams": "^6.0.5", + "pickleparser": "^0.2.1", + "playwright": "^1.32.1", + "portkey-ai": "^0.1.11", + "puppeteer": "*", + "pyodide": ">=0.24.1 <0.27.0", + "redis": "*", + "replicate": "*", + "sonix-speech-recognition": "^2.1.1", + "srt-parser-2": "^1.2.3", + "typeorm": "^0.3.20", + "typesense": "^1.5.3", + "usearch": "^1.1.1", + "voy-search": "0.6.2", + "weaviate-client": "^3.5.2", + "web-auth-library": "^1.0.3", + "word-extractor": "*", + "ws": "^8.14.2", + "youtubei.js": "*" + }, + "peerDependenciesMeta": { + "@arcjet/redact": { + "optional": true + }, + "@aws-crypto/sha256-js": { + "optional": true + }, + "@aws-sdk/client-bedrock-agent-runtime": { + "optional": true + }, + "@aws-sdk/client-bedrock-runtime": { + "optional": true + }, + "@aws-sdk/client-dynamodb": { + "optional": true + }, + "@aws-sdk/client-kendra": { + "optional": true + }, + "@aws-sdk/client-lambda": { + "optional": true + }, + "@aws-sdk/client-s3": { + "optional": true + }, + "@aws-sdk/client-sagemaker-runtime": { + "optional": true + }, + "@aws-sdk/client-sfn": { + "optional": true + }, + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@aws-sdk/dsql-signer": { + "optional": true + }, + "@azure/search-documents": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@browserbasehq/sdk": { + "optional": true + }, + "@clickhouse/client": { + "optional": true + }, + "@cloudflare/ai": { + "optional": true + }, + "@datastax/astra-db-ts": { + "optional": true + }, + "@elastic/elasticsearch": { + "optional": true + }, + "@getmetal/metal-sdk": { + "optional": true + }, + "@getzep/zep-cloud": { + "optional": true + }, + "@getzep/zep-js": { + "optional": true + }, + "@gomomento/sdk": { + "optional": true + }, + "@gomomento/sdk-core": { + "optional": true + }, + "@google-ai/generativelanguage": { + "optional": true + }, + "@google-cloud/storage": { + "optional": true + }, + "@gradientai/nodejs-sdk": { + "optional": true + }, + "@huggingface/inference": { + "optional": true + }, + "@huggingface/transformers": { + "optional": true + }, + "@lancedb/lancedb": { + "optional": true + }, + "@layerup/layerup-security": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@mendable/firecrawl-js": { + "optional": true + }, + "@mlc-ai/web-llm": { + "optional": true + }, + "@mozilla/readability": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@notionhq/client": { + "optional": true + }, + "@opensearch-project/opensearch": { + "optional": true + }, + "@pinecone-database/pinecone": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@premai/prem-sdk": { + "optional": true + }, + "@qdrant/js-client-rest": { + "optional": true + }, + "@raycast/api": { + "optional": true + }, + "@rockset/client": { + "optional": true + }, + "@smithy/eventstream-codec": { + "optional": true + }, + "@smithy/protocol-http": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "@smithy/util-utf8": { + "optional": true + }, + "@spider-cloud/spider-client": { + "optional": true + }, + "@supabase/supabase-js": { + "optional": true + }, + "@tensorflow-models/universal-sentence-encoder": { + "optional": true + }, + "@tensorflow/tfjs-converter": { + "optional": true + }, + "@tensorflow/tfjs-core": { + "optional": true + }, + "@upstash/ratelimit": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@upstash/vector": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@writerai/writer-sdk": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "@zilliz/milvus2-sdk-node": { + "optional": true + }, + "apify-client": { + "optional": true + }, + "assemblyai": { + "optional": true + }, + "azion": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "cassandra-driver": { + "optional": true + }, + "cborg": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "chromadb": { + "optional": true + }, + "closevector-common": { + "optional": true + }, + "closevector-node": { + "optional": true + }, + "closevector-web": { + "optional": true + }, + "cohere-ai": { + "optional": true + }, + "convex": { + "optional": true + }, + "crypto-js": { + "optional": true + }, + "d3-dsv": { + "optional": true + }, + "discord.js": { + "optional": true + }, + "duck-duck-scrape": { + "optional": true + }, + "epub2": { + "optional": true + }, + "fast-xml-parser": { + "optional": true + }, + "firebase-admin": { + "optional": true + }, + "google-auth-library": { + "optional": true + }, + "googleapis": { + "optional": true + }, + "hnswlib-node": { + "optional": true + }, + "html-to-text": { + "optional": true + }, + "ignore": { + "optional": true + }, + "interface-datastore": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "it-all": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "jsonwebtoken": { + "optional": true + }, + "llmonitor": { + "optional": true + }, + "lodash": { + "optional": true + }, + "lunary": { + "optional": true + }, + "mammoth": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mem0ai": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "neo4j-driver": { + "optional": true + }, + "notion-to-md": { + "optional": true + }, + "officeparser": { + "optional": true + }, + "pdf-parse": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-copy-streams": { + "optional": true + }, + "pickleparser": { + "optional": true + }, + "playwright": { + "optional": true + }, + "portkey-ai": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "pyodide": { + "optional": true + }, + "redis": { + "optional": true + }, + "replicate": { + "optional": true + }, + "sonix-speech-recognition": { + "optional": true + }, + "srt-parser-2": { + "optional": true + }, + "typeorm": { + "optional": true + }, + "typesense": { + "optional": true + }, + "usearch": { + "optional": true + }, + "voy-search": { + "optional": true + }, + "weaviate-client": { + "optional": true + }, + "web-auth-library": { + "optional": true + }, + "word-extractor": { + "optional": true + }, + "ws": { + "optional": true + }, + "youtubei.js": { + "optional": true + } + } + }, + "node_modules/@langchain/community/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/core": { + "version": "0.3.77", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.77.tgz", + "integrity": "sha512-aqXHea9xfpVn6VoCq9pjujwFqrh3vw3Fgm9KFUZJ1cF7Bx5HI62DvQPw8LlRB3NB4dhwBBA1ldAVkkkd1du8nA==", + "license": "MIT", + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.3.67", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.25.32", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/core/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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@langchain/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/core/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/langgraph": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.4.9.tgz", + "integrity": "sha512-+rcdTGi4Ium4X/VtIX3Zw4RhxEkYWpwUyz806V6rffjHOAMamg6/WZDxpJbrP33RV/wJG1GH12Z29oX3Pqq3Aw==", + "license": "MIT", + "dependencies": { + "@langchain/langgraph-checkpoint": "^0.1.1", + "@langchain/langgraph-sdk": "~0.1.0", + "uuid": "^10.0.0", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 < 0.4.0", + "zod-to-json-schema": "^3.x" + }, + "peerDependenciesMeta": { + "zod-to-json-schema": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-checkpoint": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.1.1.tgz", + "integrity": "sha512-h2bP0RUikQZu0Um1ZUPErQLXyhzroJqKRbRcxYRTAh49oNlsfeq4A3K4YEDRbGGuyPZI/Jiqwhks1wZwY73AZw==", + "license": "MIT", + "dependencies": { + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.31 <0.4.0 || ^1.0.0-alpha" + } + }, + "node_modules/@langchain/langgraph-checkpoint/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/langgraph-sdk": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.1.6.tgz", + "integrity": "sha512-PeXxfo4ls8yql6YdW8qjnZgp1giy7oqJiGjy4j2OSJ7lpkir8n62YpvADDByEh9sPzGLJYh92ZUAh0GNfQ18vA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^9.0.0" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.31 <0.4.0 || ^1.0.0-alpha", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@langchain/core": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/langgraph/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/openai": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.13.tgz", + "integrity": "sha512-t5UsO7XYE+DBQlXQ21QK74Y+LH4It20wnENrmueNvxIWTn0nHDIGVmO6wo4rJxbmOOPRQ4l/oAxGRnYU8B8v6w==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.96.0", + "zod": "3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 <0.4.0" + } + }, + "node_modules/@langchain/openai/node_modules/zod": { + "version": "3.25.32", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.32.tgz", + "integrity": "sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, + "node_modules/@langchain/weaviate": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@langchain/weaviate/-/weaviate-0.2.0.tgz", + "integrity": "sha512-gAtTCxSllR8Z92qAuRn2ir0cop241VmftQHQN+UYtTeoLge8hvZT5k0j55PDVaXTVpjx0ecx6DKv5I/wLRQI+A==", + "license": "MIT", + "dependencies": { + "uuid": "^10.0.0", + "weaviate-client": "^3.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, + "node_modules/@langchain/weaviate/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "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", @@ -1697,10 +2669,30 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@mapbox/node-pre-gyp/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/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -1715,6 +2707,86 @@ "node": ">=8.0.0" } }, + "node_modules/@playwright/test": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0.tgz", + "integrity": "sha512-15hjKreZDcp7t6TL/7jkAo6Df5STZN09jGiv5dbP9A6vMVncXRqE7/B2SncsyOwrkZRBH2i6/TPOL8BVmm3c7w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright": "1.53.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "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/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1747,6 +2819,129 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, + "node_modules/@supabase/auth-js": { + "version": "2.69.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz", + "integrity": "sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz", + "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.18.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.49.4", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.4.tgz", + "integrity": "sha512-jUF0uRUmS8BKt37t01qaZ88H9yV1mbGYnqLeuFWLcdV+x1P4fl0yP9DGtaEhFPZcwSom7u16GkLEH9QJZOqOkw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@supabase/auth-js": "2.69.1", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.2", + "@supabase/storage-js": "2.7.1" + } + }, + "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==", + "license": "MIT", + "peer": 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, + "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/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1801,6 +2996,16 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/diff-match-patch": { "version": "1.0.36", "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", @@ -1851,11 +3056,234 @@ "@types/istanbul-lib-report": "*" } }, + "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": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jest/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": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/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": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/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/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/@types/jest/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/@types/jest/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": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/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": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@types/jest/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": { + "@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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/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": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/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": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/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": { + "@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.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/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": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/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": { + "@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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/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/@types/jest/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": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", - "dev": true, "license": "MIT" }, "node_modules/@types/jsonwebtoken": { @@ -1866,6 +3294,13 @@ "@types/node": "*" } }, + "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==", + "license": "MIT", + "peer": true + }, "node_modules/@types/node": { "version": "22.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", @@ -1875,6 +3310,30 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1882,6 +3341,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -1897,6 +3383,17 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -1919,6 +3416,24 @@ "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==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abort-controller-x": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.3.tgz", + "integrity": "sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA==", + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1954,6 +3469,19 @@ "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, + "license": "MIT", + "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", @@ -1986,6 +3514,18 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "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/ai": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/ai/-/ai-4.1.13.tgz", @@ -2060,7 +3600,6 @@ "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": { "color-convert": "^2.0.1" @@ -2121,11 +3660,17 @@ "node": ">= 6" } }, + "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==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-flatten": { @@ -2153,9 +3698,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2294,6 +3839,26 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -2334,11 +3899,19 @@ "node": ">= 10.0.0" } }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -2422,6 +3995,19 @@ "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, + "license": "MIT", + "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", @@ -2433,14 +4019,39 @@ } }, "node_modules/bson": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.2.tgz", - "integrity": "sha512-5afhLTjqDSA3akH56E+/2J6kTDuSIlBxyXPdQslj9hcIgOUE378xdOfZvC/9q3LifJNI6KR/juZ+d0NRNYBwXg==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "license": "Apache-2.0", "engines": { "node": ">=16.20.1" } }, + "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", + "peer": true, + "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", @@ -2545,7 +4156,6 @@ "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", @@ -2642,7 +4252,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -2675,7 +4284,6 @@ "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": { "color-name": "~1.1.4" @@ -2688,7 +4296,6 @@ "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-support": { @@ -2745,6 +4352,15 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, + "node_modules/console-table-printer": { + "version": "2.14.3", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.14.3.tgz", + "integrity": "sha512-X5OCFnjYlXzRuC8ac5hPA2QflRjJvNKJocMhlnqK/Ap7q3DHXr0NJ0TGzwmEKOiOdJrjsSwEd0m+a32JAYPrKQ==", + "license": "MIT", + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -2846,6 +4462,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -2863,6 +4486,35 @@ "yarn": ">=1" } }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-fetch/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/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2957,6 +4609,15 @@ "ms": "2.0.0" } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -2988,7 +4649,6 @@ "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" @@ -3062,6 +4722,16 @@ "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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", @@ -3104,6 +4774,16 @@ "node": ">= 0.4" } }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -3203,6 +4883,27 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -3258,7 +4959,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3485,6 +5185,31 @@ "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/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/eventsource-parser": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", @@ -3543,6 +5268,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expr-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz", + "integrity": "sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg==", + "license": "MIT" + }, "node_modules/express": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", @@ -3840,6 +5571,13 @@ "node": ">= 0.8" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT", + "peer": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3902,6 +5640,24 @@ "node": ">=16.0.0" } }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "peer": true, + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3959,6 +5715,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -4013,6 +5778,25 @@ "node": ">= 6" } }, + "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/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.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", @@ -4072,6 +5856,20 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "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", @@ -4114,7 +5912,6 @@ "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": { "node": "6.* || 8.* || >= 10.*" @@ -4179,6 +5976,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gigachat": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/gigachat/-/gigachat-0.0.16.tgz", + "integrity": "sha512-37MFTFltKGDE1EDW6y87BxzU5orIU3fpLDqAMHCNdV8JUL2oNbHMe6CACWWqUh7HLaztwkysRP8nJxBYBms1gg==", + "license": "ISC", + "dependencies": { + "axios": "^1.8.2", + "uuid": "^11.0.3" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4243,11 +6050,54 @@ "dev": true, "license": "ISC" }, + "node_modules/graphql": { + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", + "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.2.0", + "cross-fetch": "^3.1.5" + }, + "peerDependencies": { + "graphql": "14 - 16" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "devOptional": true, + "license": "MIT", + "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/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": { "node": ">=8" @@ -4413,6 +6263,113 @@ "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/ibm-cloud-sdk-core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.4.0.tgz", + "integrity": "sha512-c4cwOuUDbMiFROYM/Ti1aC+Umi1v3TdvC2DO5zR7w44FYY/3xrs79+3DVPXt/nRhJeaMHN2L9XwlXsPSoVDHJA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/debug": "^4.1.12", + "@types/node": "^18.19.80", + "@types/tough-cookie": "^4.0.0", + "axios": "^1.8.2", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "extend": "3.0.2", + "file-type": "16.5.4", + "form-data": "4.0.0", + "isstream": "0.1.2", + "jsonwebtoken": "^9.0.2", + "mime-types": "2.1.35", + "retry-axios": "^2.6.0", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/@types/node": { + "version": "18.19.111", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.111.tgz", + "integrity": "sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ibm-cloud-sdk-core/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", + "peer": true + }, + "node_modules/ibm-cloud-sdk-core/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ibm-cloud-sdk-core/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", + "peer": true + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -4424,11 +6381,32 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, "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, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4627,6 +6605,13 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT", + "peer": 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", @@ -5322,6 +7307,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-tiktoken": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.20.tgz", + "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5333,7 +7327,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5461,27 +7454,6 @@ "node": ">=18" } }, - "node_modules/jsdom/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "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/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -5571,6 +7543,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -5646,6 +7627,190 @@ "node": ">=6" } }, + "node_modules/langchain": { + "version": "0.3.34", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.34.tgz", + "integrity": "sha512-OADHLQYRX+36EqQBxIoryCdMKfHex32cJBSWveadIIeRhygqivacIIDNwVjX51Y++c80JIdR0jaQHWn2r3H1iA==", + "license": "MIT", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.7.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": "^0.3.67", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cerebras": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.3.58 <0.4.0", + "@langchain/deepseek": "*", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/google-vertexai-web": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "@langchain/xai": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cerebras": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/deepseek": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/google-vertexai-web": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "@langchain/xai": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/langchain-gigachat": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/langchain-gigachat/-/langchain-gigachat-0.0.14.tgz", + "integrity": "sha512-8jnHMZI1QqAs98iTdldouT1chiFRTtEnxXHFiQl8th7u/B6Eot0OJfMT5iviCFO6/pMNxYgq0Fzzr29ndaJyEQ==", + "license": "MIT", + "dependencies": { + "gigachat": "^0.0.15", + "uuid": "^11.0.5", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.23.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, + "node_modules/langchain-gigachat/node_modules/gigachat": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/gigachat/-/gigachat-0.0.15.tgz", + "integrity": "sha512-4hAf/obnzwW4xp+AOP6Zv81F3Dr9QcsEjVOGTdY4aRWphzgV8YVZ134huqQfA/LQCuoD9UMmlt3nfix6exgjYg==", + "license": "ISC", + "dependencies": { + "axios": "^1.8.2", + "uuid": "^11.0.3" + } + }, + "node_modules/langchain/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/langsmith": { + "version": "0.3.69", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.69.tgz", + "integrity": "sha512-YKzu92YAP2o+d+1VmR38xqFX0RIRLKYj1IqdflVEY83X0FoiVlrWO3xDLXgnu7vhZ2N2M6jx8VO9fVF8yy9gHA==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/langsmith/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5699,6 +7864,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "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.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -5729,6 +7900,13 @@ "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, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5741,16 +7919,11 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, - "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==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } + "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/make-dir": { "version": "3.1.0", @@ -5774,6 +7947,13 @@ "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, + "license": "ISC" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -5950,14 +8130,14 @@ } }, "node_modules/mongodb": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.13.0.tgz", - "integrity": "sha512-KeESYR5TEaFxOuwRqkOm3XOsMqCSkdeDMjaW5u2nuKfX7rqaofp7JQGoi7sVqQcNJTKuveNbzZtWMstb8ABP6Q==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.1", - "mongodb-connection-string-url": "^3.0.0" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { "node": ">=16.20.1" @@ -5968,7 +8148,7 @@ "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", + "snappy": "^7.3.2", "socks": "^2.7.1" }, "peerDependenciesMeta": { @@ -6040,14 +8220,14 @@ } }, "node_modules/mongoose": { - "version": "8.9.5", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.5.tgz", - "integrity": "sha512-SPhOrgBm0nKV3b+IIHGqpUTOmgVL5Z3OO9AwkFEmvOZznXTvplbomstCnPOGAyungtRXE5pJTgKpKcZTdjeESg==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.2.tgz", + "integrity": "sha512-gA6GFlshOHUdNyw9OQTmMLSGzVOPbcbjaSZ1dvR5iMp668N2UUznTuzgTY6V6Q41VtBc4kmL/qqML1RNgXB5Fg==", "license": "MIT", "dependencies": { - "bson": "^6.10.1", + "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "~6.12.0", + "mongodb": "~6.18.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -6075,13 +8255,13 @@ } }, "node_modules/mongoose/node_modules/mongodb": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", - "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz", + "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.1", + "bson": "^6.10.4", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -6127,16 +8307,16 @@ "license": "MIT" }, "node_modules/morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "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", "depd": "~2.0.0", "on-finished": "~2.3.0", - "on-headers": "~1.0.2" + "on-headers": "~1.1.0" }, "engines": { "node": ">= 0.8.0" @@ -6154,6 +8334,15 @@ "node": ">= 0.8" } }, + "node_modules/morgan/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" + } + }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -6233,6 +8422,15 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -6266,28 +8464,66 @@ "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/nice-grpc": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.12.tgz", + "integrity": "sha512-J1n4Wg+D3IhRhGQb+iqh2OpiM0GzTve/kf2lnlW4S+xczmIEd0aHUDV1OsJ5a3q8GSTqJf+s4Rgg1M8uJltarw==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.13.1", + "abort-controller-x": "^0.4.0", + "nice-grpc-common": "^2.0.2" + } + }, + "node_modules/nice-grpc-client-middleware-retry": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nice-grpc-client-middleware-retry/-/nice-grpc-client-middleware-retry-3.1.11.tgz", + "integrity": "sha512-xW/imz/kNG2g0DwTfH2eYEGrg1chSLrXtvGp9fg2qkhTgGFfAS/Pq3+t+9G8KThcC4hK/xlEyKvZWKk++33S6A==", + "license": "MIT", + "dependencies": { + "abort-controller-x": "^0.4.0", + "nice-grpc-common": "^2.0.2" + } + }, + "node_modules/nice-grpc-common": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.2.tgz", + "integrity": "sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==", + "license": "MIT", + "dependencies": { + "ts-error": "^1.0.6" + } + }, "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-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", - "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-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-int64": { @@ -6493,6 +8729,77 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "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" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.111", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.111.tgz", + "integrity": "sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/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/openai/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/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==", + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6511,6 +8818,15 @@ "node": ">= 0.8.0" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6543,6 +8859,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -6654,10 +9011,24 @@ "fastfall": "^1.2.3" } }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "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" }, @@ -6752,6 +9123,38 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0.tgz", + "integrity": "sha512-ghGNnIEYZC4E+YtclRn4/p6oYbdPiASELBIYkBXfaTVKreQUYbMUYQDwS12a8F0/HtIjr/CkGjtwABeFPGcS4Q==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright-core": "1.53.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0.tgz", + "integrity": "sha512-mGLg8m0pm4+mmtB7M89Xw/GSqoNC+twivl8ITteqvAndachozYe2ZA7srU6uleV1vEdAHYqjq+SV8SNxRRFYBw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6790,6 +9193,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -6810,6 +9223,30 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "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", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -6828,6 +9265,19 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -6875,6 +9325,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT", + "peer": true + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -6944,6 +9401,50 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "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==", + "license": "MIT", + "peer": 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==", + "license": "MIT", + "peer": 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/readable-web-to-node-stream/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", + "peer": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -6960,12 +9461,18 @@ "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" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT", + "peer": true + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -7027,6 +9534,28 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-axios": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", + "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.7.0" + }, + "peerDependencies": { + "axios": "*" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -7128,12 +9657,10 @@ "license": "BSD-3-Clause" }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.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" }, @@ -7335,6 +9862,12 @@ "node": ">=10" } }, + "node_modules/simple-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==", + "license": "MIT" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -7403,6 +9936,27 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "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.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -7461,7 +10015,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7615,6 +10169,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/superagent": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", @@ -7692,7 +10264,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -7832,6 +10403,24 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -7876,6 +10465,214 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-error": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", + "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "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": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@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 + }, + "babel-jest": { + "optional": true + }, + "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, + "license": "(MIT OR CC0-1.0)", + "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", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "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/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tsconfig/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": { + "node": ">=4" + } + }, + "node_modules/tsconfig/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7930,6 +10727,34 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "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, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "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", @@ -7953,6 +10778,16 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "license": "MIT" }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8002,6 +10837,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", @@ -8037,6 +10883,13 @@ "uuid": "dist/esm/bin/uuid" } }, + "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", @@ -8082,6 +10935,47 @@ "makeerror": "1.0.12" } }, + "node_modules/weaviate-client": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/weaviate-client/-/weaviate-client-3.6.1.tgz", + "integrity": "sha512-C/sVYLqLuRQn2FeP3YQFNSOV61gNNtswNDRnb117ZfweSU/DIGAYeZZn63DpNI11qDSWTS3Cbz/aQjUzZXbA6Q==", + "license": "BSD 3-Clause", + "dependencies": { + "abort-controller-x": "^0.4.3", + "graphql": "^16.10.0", + "graphql-request": "^6.1.0", + "long": "^5.2.4", + "nice-grpc": "^2.1.11", + "nice-grpc-client-middleware-retry": "^3.1.10", + "nice-grpc-common": "^2.0.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/weaviate-client/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "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", @@ -8161,11 +11055,17 @@ "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==", + "devOptional": true, + "license": "MIT" + }, "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": { "ansi-styles": "^4.0.0", @@ -8199,9 +11099,9 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -8246,7 +11146,6 @@ "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": { "node": ">=10" @@ -8257,11 +11156,22 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.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": { "cliui": "^8.0.1", @@ -8280,12 +11190,21 @@ "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": { "node": ">=12" } }, + "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": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -8300,11 +11219,10 @@ } }, "node_modules/zod": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "version": "3.25.63", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.63.tgz", + "integrity": "sha512-3ttCkqhtpncYXfP0f6dsyabbYV/nEUW+Xlu89jiXbTBifUfjaSqXOG6JnQPLtqt87n7KAmnMqcjay6c0Wq0Vbw==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index c7384b9..4dca919 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,13 @@ { "name": "multi-stub", - "version": "1.2.1", + "version": "2.0.0", "description": "", - "main": "index.js", + "main": "server/index.ts", + "type": "commonjs", "scripts": { - "start": "cross-env PORT=8033 npx nodemon ./server", - "up:prod": "cross-env NODE_ENV=\"production\" node ./server", - "deploy:d:stop": "docker compose down", - "deploy:d:build": "docker compose build", - "deploy:d:up": "docker compose up -d", - "redeploy": "npm run deploy:d:stop && npm run deploy:d:build && npm run deploy:d:up", + "start": "cross-env NODE_ENV=\"development\" ts-node-dev .", + "build": "tsc", + "up:prod": "node dist/server/index.js", "eslint": "npx eslint ./server", "eslint:fix": "npx eslint ./server --fix", "test": "jest" @@ -23,9 +21,13 @@ "license": "MIT", "homepage": "https://bitbucket.org/online-mentor/multi-stub#readme", "dependencies": { + "@langchain/community": "^0.3.56", + "@langchain/core": "^0.3.77", + "@langchain/langgraph": "^0.4.9", "ai": "^4.1.13", "axios": "^1.7.7", "bcrypt": "^5.1.0", + "bcryptjs": "^3.0.3", "body-parser": "^1.19.0", "cookie-parser": "^1.4.5", "cors": "^2.8.5", @@ -35,26 +37,33 @@ "express": "5.0.1", "express-jwt": "^8.5.1", "express-session": "^1.18.1", + "gigachat": "^0.0.16", "jsdom": "^25.0.1", "jsonwebtoken": "^9.0.2", - "mongodb": "^6.12.0", - "mongoose": "^8.9.2", + "langchain": "^0.3.34", + "langchain-gigachat": "^0.0.14", + "mongodb": "^6.20.0", + "mongoose": "^8.18.2", "mongoose-sequence": "^6.0.1", - "morgan": "^1.10.0", + "morgan": "^1.10.1", "multer": "^1.4.5-lts.1", "pbkdf2-password": "^1.2.1", "rotating-file-stream": "^3.2.5", "socket.io": "^4.8.1", - "uuid": "^11.0.3" + "zod": "^3.24.3" }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/jest": "^30.0.0", "@types/node": "22.10.2", "eslint": "^9.17.0", "globals": "^15.14.0", "jest": "^29.7.0", "mockingoose": "^2.16.2", "nodemon": "3.1.9", - "supertest": "^7.0.0" + "supertest": "^7.0.0", + "ts-jest": "^29.4.6", + "ts-node-dev": "2.0.0", + "typescript": "5.7.3" } } diff --git a/rules.md b/rules.md new file mode 100644 index 0000000..ece8fda --- /dev/null +++ b/rules.md @@ -0,0 +1,87 @@ +## Правила оформления студенческих бэкендов в `multi-stub` + +Этот документ описывает, как подключать новый студенческий бэкенд к общему серверу и как работать с JSON‑заглушками. Правила написаны так, чтобы их мог автоматически выполнять помощник Cursor. + +### 1. Общая структура проекта студента + +- **Размещение проекта** + - Каждый студенческий бэкенд живёт в своей подпапке в `server/routers/`. + - В корне подпапки должен быть основной файл роутера `index.js` (или `index.ts`), который экспортирует `express.Router()`. + - Подключение к общему серверу выполняется в `server/index.ts` через импорт и `app.use(, )`. + +- **Использование JSON‑заглушек** + - Если проект переносится из фронтенд‑репозитория и должен только отдавать данные, то в подпапке проекта должна быть папка `json/` со всеми нужными `.json` файлами. + - HTTP‑обработчики в роутере могут просто читать и возвращать содержимое этих файлов (например, через `require('./json/...')` или `import data from './json/...json'` с включённым `resolveJsonModule` / соответствующей конфигурацией bundler'а). + +### 2. Правила для Cursor при указании директории заглушек + +Когда пользователь явно указывает директорию с заглушками (например: `server/routers//json`), помощник Cursor должен последовательно выполнить следующие шаги. + +- **2.1. Проверка валидности импортов JSON‑файлов** + - Найти все `.js` / `.ts` файлы внутри подпапки проекта. + - В каждом таком файле найти импорты/require, которые ссылаются на `.json` файлы (относительные пути вроде `'./json/.../file.json'`). + - Для каждого такого импорта: + - **Проверить, что файл реально существует** по указанному пути относительно файла-импортёра. + - **Проверить расширение**: путь должен заканчиваться на `.json` (без опечаток). + - **Проверить регистр и точное совпадение имени файла** (важно для кросс‑платформенности, даже если локально используется Windows). + - Если найдены ошибки (файл не существует, опечатка в имени, неправильный относительный путь и т.п.): + - Сформировать понятный список проблем: в каком файле, какая строка/импорт и что именно не так. + - Предложить автоматически исправить пути (если по контексту можно однозначно угадать нужный `*.json` файл). + +- **2.2. Проверка подключения основного роутера проекта** + - Определить основной файл роутера проекта: + - По умолчанию это `server/routers//index.js` (или `index.ts`). + - Открыть `server/index.ts` и убедиться, что: + - Есть импорт роутера из соответствующей подпапки, например: + - `import Router from './routers/'` + - или `const Router = require('./routers/')` + - Имя переменной роутера **уникально** среди всех импортов роутеров (нет другого импорта с таким же именем). + - Есть вызов `app.use('', Router)`: + - `` должен быть осмысленным, совпадать с названием проекта или оговариваться пользователем. + - Если импорт или `app.use` отсутствуют: + - Сформировать предложение по добавлению корректного импорта и `app.use(...)`. + - Убедиться, что используемое имя роутера не конфликтует с уже существующими. + - Если обнаружен конфликт имён: + - Предложить переименовать новый роутер в уникальное имя и обновить соответствующие места в `server/index.ts`. + +### 3. Предложение «оживить» JSON‑заглушки + +После того как проверка импортов и подключения роутера завершена, помощник Cursor должен **задать пользователю вопрос**, не хочет ли он превратить заглушки в полноценный бэкенд. + +- **3.1. Формулировка предложения** + - Спросить у пользователя примерно так: + - «Обнаружены JSON‑заглушки в директории `<указанная-папка>`. Хотите, чтобы я попытался автоматически: + 1) построить модели данных (mongoose‑схемы) на основе структуры JSON; + 2) создать CRUD‑эндпоинты и/или более сложные маршруты, опираясь на существующие данные; + 3) заменить прямую отдачу `*.json` файлов на работу через базу данных?» + +- **3.2. Поведение при согласии пользователя** + - Проанализировать структуру JSON‑файлов: + - Определить основные сущности и поля. + - Выделить типы полей (строки, числа, даты, массивы, вложенные объекты и т.п.). + - На основе анализа предложить: + - Набор `mongoose`‑схем (`models`) с аккуратной сериализацией (виртуальное поле `id`, скрытие `_id` и `__v`). + - Набор маршрутов `express` для работы с этими моделями (минимум: чтение списков и элементов; по возможности — создание/обновление/удаление). + - Перед внесением изменений: + - Показать пользователю краткий план того, какие файлы будут созданы/изменены. + - Выполнить изменения только после явного подтверждения пользователя. + +### 4. Минимальные требования к новому студенческому бэкенду + +- **Обязательные элементы** + - Подпапка в `server/routers/`. + - Основной роутер `index.js` / `index.ts`, экспортирующий `express.Router()`. + - Подключение к общему серверу в `server/index.ts` (импорт + `app.use()` с уникальным именем роутера). + +- **Если используются JSON‑заглушки** + - Папка `json/` внутри проекта. + - Все пути в импортирующих файлах должны указывать на реально существующие `*.json` файлы. + - Не должно быть «магических» абсолютных путей; только относительные пути от файла до нужного JSON. + +- **Если проект «оживлён»** + - Папка `model/` с моделью(ями) данных (например, через `mongoose`). + - Роуты, которые вместо прямой отдачи файлов работают с моделями и, при необходимости, с внешними сервисами. + +Следуя этим правилам, можно подключать новые студенческие проекты в единый бэкенд, минимизировать типичные ошибки с путями к JSON и упростить автоматическое развитие заглушек до полноценного API. + + diff --git a/server/__tests__/__snapshots__/todo.test.js.snap b/server/__tests__/__snapshots__/todo.test.js.snap index 2573ff1..46ffa2a 100644 --- a/server/__tests__/__snapshots__/todo.test.js.snap +++ b/server/__tests__/__snapshots__/todo.test.js.snap @@ -4,7 +4,6 @@ exports[`todo list app get list 1`] = ` { "body": [ { - "_id": "670f69b5796ce7a9069da2f7", "created": "2024-10-16T07:22:29.042Z", "id": "670f69b5796ce7a9069da2f7", "items": [], diff --git a/server/__tests__/todo.test.js b/server/__tests__/todo.test.js index 8ee1701..8972cb3 100644 --- a/server/__tests__/todo.test.js +++ b/server/__tests__/todo.test.js @@ -2,7 +2,7 @@ const { describe, it, expect } = require('@jest/globals') const request = require('supertest') const express = require('express') const mockingoose = require('mockingoose') -const { ListModel } = require('../data/model/todo/list') +const { ListModel } = require('../routers/todo/model/todo/list') const todo = require('../routers/todo/routes') diff --git a/server/error.js b/server/error.js deleted file mode 100644 index b8db25c..0000000 --- a/server/error.js +++ /dev/null @@ -1,13 +0,0 @@ -const noToken = 'No authorization token was found' - -module.exports = (err, req, res, next) => { - if (err.message === noToken) { - res.status(400).send({ - success: false, error: 'Токен авторизации не найден', - }) - } - - res.status(400).send({ - success: false, error: err.message || 'Что-то пошло не так', - }) -} diff --git a/server/error.ts b/server/error.ts new file mode 100644 index 0000000..1919737 --- /dev/null +++ b/server/error.ts @@ -0,0 +1,28 @@ +import { ErrorLog } from './models/ErrorLog' + +const noToken = 'No authorization token was found' + +export const errorHandler = (err, req, res, next) => { + // Сохраняем ошибку в базу данных + const errorLog = new ErrorLog({ + message: err.message || 'Неизвестная ошибка', + stack: err.stack, + path: req.path, + method: req.method, + query: req.query, + body: req.body + }) + + errorLog.save() + .catch(saveErr => console.error('Ошибка при сохранении лога ошибки:', saveErr)) + + if (err.message === noToken) { + res.status(400).send({ + success: false, error: 'Токен авторизации не найден', + }) + } + + res.status(400).send({ + success: false, error: err.message || 'Что-то пошло не так', + }) +} diff --git a/server/index.js b/server/index.js deleted file mode 100644 index 2a179cd..0000000 --- a/server/index.js +++ /dev/null @@ -1,99 +0,0 @@ -const express = require("express") -const bodyParser = require("body-parser") -const cookieParser = require("cookie-parser") -const session = require("express-session") -const morgan = require("morgan") -const path = require("path") -const rfs = require("rotating-file-stream") - -const app = express() -require("dotenv").config() -exports.app = app - -const accessLogStream = rfs.createStream("access.log", { - size: "10M", - interval: "1d", - compress: "gzip", - path: path.join(__dirname, "log"), -}) - -const errorLogStream = rfs.createStream("error.log", { - size: "10M", - interval: "1d", - compress: "gzip", - path: path.join(__dirname, "log"), -}) - -const config = require("../.serverrc") -const { setIo } = require("./io") - -app.use(cookieParser()) -app.use( - morgan("combined", { - stream: accessLogStream, - skip: function (req, res) { - return res.statusCode >= 400 - }, - }) -) - -// log all requests to access.log -app.use( - morgan("combined", { - stream: errorLogStream, - skip: function (req, res) { - console.log('statusCode', res.statusCode, res.statusCode <= 400) - return res.statusCode < 400 - }, - }) -) - -const server = setIo(app) - -const sess = { - secret: "super-secret-key", - resave: true, - saveUninitialized: true, - cookie: {}, -} -if (app.get("env") === "production") { - app.set("trust proxy", 1) - sess.cookie.secure = true -} -app.use(session(sess)) - -app.use( - bodyParser.json({ - limit: "50mb", - }) -) -app.use( - bodyParser.urlencoded({ - limit: "50mb", - extended: true, - }) -) -app.use(require("./root")) - -/** - * Добавляйте сюда свои routers. - */ -app.use("/kfu-m-24-1", require("./routers/kfu-m-24-1")) -app.use("/epja-2024-1", require("./routers/epja-2024-1")) -app.use("/v1/todo", require("./routers/todo")) -app.use("/dogsitters-finder", require("./routers/dogsitters-finder")) -app.use("/kazan-explore", require("./routers/kazan-explore")) -app.use("/edateam", require("./routers/edateam-legacy")) -app.use("/dry-wash", require("./routers/dry-wash")) -app.use("/freetracker", require("./routers/freetracker")) -app.use("/dhs-testing", require("./routers/dhs-testing")) -app.use("/gamehub", require("./routers/gamehub")) -app.use("/esc", require("./routers/esc")) -app.use('/connectme', require('./routers/connectme')) -app.use('/questioneer', require('./routers/questioneer')) - -app.use(require("./error")) - -server.listen(config.port, () => - console.log(`Listening on http://localhost:${config.port}`) -) diff --git a/server/index.ts b/server/index.ts new file mode 100644 index 0000000..e24449d --- /dev/null +++ b/server/index.ts @@ -0,0 +1,157 @@ +import express from 'express' +import cookieParser from 'cookie-parser' +import session from 'express-session' +import morgan from 'morgan' +import path from 'path' +import 'dotenv/config' + +import root from './server' +import { errorHandler } from './error' +import kfuM241Router from './routers/kfu-m-24-1' +import epja20241Router from './routers/epja-2024-1' +import todoRouter from './routers/todo' +import dogsittersFinderRouter from './routers/dogsitters-finder' +import kazanExploreRouter from './routers/kazan-explore' +import edateamRouter from './routers/edateam-legacy' +import dryWashRouter from './routers/dry-wash' +import freetrackerRouter from './routers/freetracker' +import dhsTestingRouter from './routers/dhs-testing' +import gamehubRouter from './routers/gamehub' +import escRouter from './routers/esc' +import connectmeRouter from './routers/connectme' +import questioneerRouter from './routers/questioneer' +import procurementRouter from './routers/procurement' +import smokeTrackerRouter from './routers/smoke-tracker' +import assessmentToolsRouter from './routers/assessment-tools' +import { setIo } from './io' + +export const app = express() + +// Динамический импорт rotating-file-stream +const initServer = async () => { + const rfs = await import('rotating-file-stream') + const accessLogStream = rfs.createStream("access.log", { + size: "10M", + interval: "1d", + compress: "gzip", + path: path.join(__dirname, "log"), + }) + + const errorLogStream = rfs.createStream("error.log", { + size: "10M", + interval: "1d", + compress: "gzip", + path: path.join(__dirname, "log"), + }) + + app.use(cookieParser()) + app.use( + morgan("combined", { + stream: accessLogStream, + skip: function (req, res) { + return res.statusCode >= 400 + }, + }) + ) + + // log all requests to access.log + app.use( + morgan("combined", { + stream: errorLogStream, + skip: function (req, res) { + console.log('statusCode', res.statusCode, res.statusCode <= 400) + return res.statusCode < 400 + }, + }) + ) + + console.log('warming up 🔥') + + const sess = { + secret: "super-secret-key", + resave: true, + saveUninitialized: true, + cookie: {}, + } + if (app.get("env") !== "development") { + app.set("trust proxy", 1) + } + app.use(session(sess)) + + app.use( + express.json({ + limit: "50mb", + }) + ) + app.use( + express.urlencoded({ + limit: "50mb", + extended: true, + }) + ) + app.use(root) + + + /** + * Добавляйте сюда свои routers. + */ + app.use("/kfu-m-24-1", kfuM241Router) + app.use("/epja-2024-1", epja20241Router) + app.use("/v1/todo", todoRouter) + app.use("/dogsitters-finder", dogsittersFinderRouter) + app.use("/kazan-explore", kazanExploreRouter) + app.use("/edateam", edateamRouter) + app.use("/dry-wash", dryWashRouter) + app.use("/freetracker", freetrackerRouter) + app.use("/dhs-testing", dhsTestingRouter) + app.use("/gamehub", gamehubRouter) + app.use("/esc", escRouter) + app.use('/connectme', connectmeRouter) + app.use('/questioneer', questioneerRouter) + app.use('/procurement', procurementRouter) + app.use('/smoke-tracker', smokeTrackerRouter) + app.use('/assessment-tools', assessmentToolsRouter) + app.use(errorHandler) + + // Создаем обычный HTTP сервер + const server = app.listen(process.env.PORT ?? 8044, () => { + console.log(`🚀 Сервер запущен на http://localhost:${process.env.PORT ?? 8044}`) + }) + + // Обработка сигналов завершения процесса + process.on('SIGTERM', () => { + console.log('🛑 Получен сигнал SIGTERM. Выполняется корректное завершение...') + server.close(() => { + console.log('✅ Сервер успешно остановлен') + process.exit(0) + }) + }) + + process.on('SIGINT', () => { + console.log('🛑 Получен сигнал SIGINT. Выполняется корректное завершение...') + server.close(() => { + console.log('✅ Сервер успешно остановлен') + process.exit(0) + }) + }) + + // Обработка необработанных исключений + process.on('uncaughtException', (err) => { + console.error('❌ Необработанное исключение:', err) + server.close(() => { + process.exit(1) + }) + }) + + // Обработка необработанных отклонений промисов + process.on('unhandledRejection', (reason, promise) => { + console.error('⚠️ Необработанное отклонение промиса:', reason) + server.close(() => { + process.exit(1) + }) + }) + + return server +} + +initServer().catch(console.error) diff --git a/server/io.js b/server/io.js deleted file mode 100644 index 8df2c10..0000000 --- a/server/io.js +++ /dev/null @@ -1,13 +0,0 @@ -const { Server } = require('socket.io') -const { createServer } = require('http') - -let io = null - -module.exports.setIo = (app) => { - const server = createServer(app) - io = new Server(server, {}) - - return server -} - -module.exports.getIo = () => io diff --git a/server/io.ts b/server/io.ts new file mode 100644 index 0000000..71833d6 --- /dev/null +++ b/server/io.ts @@ -0,0 +1,13 @@ +import { Server } from 'socket.io' +import { createServer } from 'http' + +let io = null + +export const setIo = (app) => { + const server = createServer(app) + io = new Server(server, {}) + + return server +} + +export const getIo = () => io diff --git a/server/models/ErrorLog.ts b/server/models/ErrorLog.ts new file mode 100644 index 0000000..5923615 --- /dev/null +++ b/server/models/ErrorLog.ts @@ -0,0 +1,16 @@ +import mongoose from 'mongoose' + +const ErrorLogSchema = new mongoose.Schema({ + message: { type: String, required: true }, + stack: { type: String }, + path: { type: String }, + method: { type: String }, + query: { type: Object }, + body: { type: Object }, + createdAt: { type: Date, default: Date.now }, +}) + +// Индекс для быстрого поиска по дате создания +ErrorLogSchema.index({ createdAt: 1 }) + +export const ErrorLog = mongoose.model('ErrorLog', ErrorLogSchema) \ No newline at end of file diff --git a/server/models/questionnaire.js b/server/models/questionnaire.ts similarity index 88% rename from server/models/questionnaire.js rename to server/models/questionnaire.ts index e08928e..aa39e23 100644 --- a/server/models/questionnaire.js +++ b/server/models/questionnaire.ts @@ -1,7 +1,7 @@ const mongoose = require('mongoose'); // Типы вопросов -const QUESTION_TYPES = { +export const QUESTION_TYPES = { SINGLE_CHOICE: 'single_choice', // Один вариант MULTIPLE_CHOICE: 'multiple_choice', // Несколько вариантов TEXT: 'text', // Текстовый ответ @@ -10,7 +10,7 @@ const QUESTION_TYPES = { }; // Типы отображения -const DISPLAY_TYPES = { +export const DISPLAY_TYPES = { DEFAULT: 'default', TAG_CLOUD: 'tag_cloud', VOTING: 'voting', @@ -51,10 +51,5 @@ const questionnaireSchema = new mongoose.Schema({ publicLink: { type: String, required: true } // ссылка для голосования }); -const Questionnaire = mongoose.model('Questionnaire', questionnaireSchema); +export const Questionnaire = mongoose.model('Questionnaire', questionnaireSchema); -module.exports = { - Questionnaire, - QUESTION_TYPES, - DISPLAY_TYPES -}; \ No newline at end of file diff --git a/server/root.js b/server/root.js deleted file mode 100644 index cb3cc8a..0000000 --- a/server/root.js +++ /dev/null @@ -1,33 +0,0 @@ -const fs = require('fs') -const path = require('path') -const router = require('express').Router() -const mongoose = require('mongoose') - -const pkg = require('../package.json') - -require('./utils/mongoose') -const folderPath = path.resolve(__dirname, './routers') -const folders = fs.readdirSync(folderPath) - -router.get('/', async (req, res) => { - // throw new Error('check error message') - res.send(` -

multy stub is working v${pkg.version}

-
    - ${folders.map((f) => `
  • ${f}
  • `).join('')} -
- -

models

-
    ${ - (await Promise.all( - (await mongoose.modelNames()).map(async (name) => { - const count = await mongoose.model(name).countDocuments() - return `
  • ${name} - ${count}
  • ` - } - ) - )).map(t => t).join(' ') - }
- `) -}) - -module.exports = router diff --git a/server/routers/assessment-tools/index.js b/server/routers/assessment-tools/index.js new file mode 100644 index 0000000..8104b39 --- /dev/null +++ b/server/routers/assessment-tools/index.js @@ -0,0 +1,18 @@ +const router = require('express').Router(); + +// Импортировать mongoose из общего модуля (подключение происходит в server/utils/mongoose.ts) +const mongoose = require('../../utils/mongoose'); + +const timer = (time = 300) => (req, res, next) => setTimeout(next, time); + +router.use(timer()); + +// Подключение маршрутов - прямые пути без path.join и __dirname +router.use('/events', require('./routes/event')); +router.use('/teams', require('./routes/teams')); +router.use('/experts', require('./routes/experts')); +router.use('/criteria', require('./routes/criteria')); +router.use('/ratings', require('./routes/ratings')); + +module.exports = router; +module.exports.default = router; diff --git a/server/routers/assessment-tools/models/Criteria.js b/server/routers/assessment-tools/models/Criteria.js new file mode 100644 index 0000000..6d80c71 --- /dev/null +++ b/server/routers/assessment-tools/models/Criteria.js @@ -0,0 +1,50 @@ +const mongoose = require('mongoose'); + +const criterionItemSchema = new mongoose.Schema({ + name: { + type: String, + required: true + }, + maxScore: { + type: Number, + default: 5, + min: 0, + max: 10 + } +}, { _id: false }); + +const criteriaSchema = new mongoose.Schema({ + eventId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Event', + required: true + }, + blockName: { + type: String, + required: true + }, + criteriaType: { + type: String, + enum: ['team', 'participant', 'all'], + default: 'all', + required: true + }, + criteria: [criterionItemSchema], + order: { + type: Number, + default: 0 + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +module.exports = mongoose.model('Criteria', criteriaSchema); + diff --git a/server/routers/assessment-tools/models/Event.js b/server/routers/assessment-tools/models/Event.js new file mode 100644 index 0000000..6d504a8 --- /dev/null +++ b/server/routers/assessment-tools/models/Event.js @@ -0,0 +1,44 @@ +const mongoose = require('mongoose'); + +const eventSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + default: 'Новое мероприятие' + }, + description: { + type: String, + default: '' + }, + eventDate: { + type: Date, + required: true, + default: Date.now + }, + location: { + type: String, + default: '' + }, + status: { + type: String, + enum: ['draft', 'ready', 'active', 'completed'], + default: 'draft' + }, + votingEnabled: { + type: Boolean, + default: false + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +module.exports = mongoose.model('Event', eventSchema); + diff --git a/server/routers/assessment-tools/models/Expert.js b/server/routers/assessment-tools/models/Expert.js new file mode 100644 index 0000000..a774160 --- /dev/null +++ b/server/routers/assessment-tools/models/Expert.js @@ -0,0 +1,43 @@ +const mongoose = require('mongoose'); +const crypto = require('crypto'); + +const expertSchema = new mongoose.Schema({ + eventId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Event', + required: true + }, + fullName: { + type: String, + required: true + }, + token: { + type: String, + unique: true + }, + qrCodeUrl: { + type: String, + default: '' + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +// Generate unique token before saving +expertSchema.pre('save', function(next) { + if (!this.token) { + this.token = crypto.randomBytes(16).toString('hex'); + } + next(); +}); + +module.exports = mongoose.model('Expert', expertSchema); + diff --git a/server/routers/assessment-tools/models/Rating.js b/server/routers/assessment-tools/models/Rating.js new file mode 100644 index 0000000..b5a2539 --- /dev/null +++ b/server/routers/assessment-tools/models/Rating.js @@ -0,0 +1,64 @@ +const mongoose = require('mongoose'); + +const ratingItemSchema = new mongoose.Schema({ + criteriaId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Criteria', + required: true + }, + criterionName: { + type: String, + required: true + }, + score: { + type: Number, + required: true, + min: 0, + max: 5 + } +}, { _id: false }); + +const ratingSchema = new mongoose.Schema({ + eventId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Event', + required: true + }, + expertId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Expert', + required: true + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Team', + required: true + }, + ratings: [ratingItemSchema], + totalScore: { + type: Number, + default: 0 + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +// Calculate total score before saving +ratingSchema.pre('save', function(next) { + this.totalScore = this.ratings.reduce((sum, item) => sum + item.score, 0); + next(); +}); + +// Ensure unique combination of expert and team +ratingSchema.index({ expertId: 1, teamId: 1 }, { unique: true }); + +module.exports = mongoose.model('Rating', ratingSchema); + diff --git a/server/routers/assessment-tools/models/Team.js b/server/routers/assessment-tools/models/Team.js new file mode 100644 index 0000000..abb2a6c --- /dev/null +++ b/server/routers/assessment-tools/models/Team.js @@ -0,0 +1,52 @@ +const mongoose = require('mongoose'); + +const teamSchema = new mongoose.Schema({ + eventId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Event', + required: true + }, + type: { + type: String, + enum: ['team', 'participant'], + required: true + }, + name: { + type: String, + required: true + }, + projectName: { + type: String, + default: '' + }, + caseDescription: { + type: String, + default: '' + }, + isActive: { + type: Boolean, + default: true + }, + votingStatus: { + type: String, + enum: ['not_evaluated', 'evaluating', 'evaluated'], + default: 'not_evaluated' + }, + isActiveForVoting: { + type: Boolean, + default: false + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +module.exports = mongoose.model('Team', teamSchema); + diff --git a/server/routers/assessment-tools/models/index.js b/server/routers/assessment-tools/models/index.js new file mode 100644 index 0000000..6f05978 --- /dev/null +++ b/server/routers/assessment-tools/models/index.js @@ -0,0 +1,14 @@ +const Event = require('./Event'); +const Team = require('./Team'); +const Expert = require('./Expert'); +const Criteria = require('./Criteria'); +const Rating = require('./Rating'); + +module.exports = { + Event, + Team, + Expert, + Criteria, + Rating +}; + diff --git a/server/routers/assessment-tools/routes/criteria.js b/server/routers/assessment-tools/routes/criteria.js new file mode 100644 index 0000000..49458f2 --- /dev/null +++ b/server/routers/assessment-tools/routes/criteria.js @@ -0,0 +1,152 @@ +const router = require('express').Router(); +const { Criteria } = require('../models'); + +// Критерии по умолчанию из hack.md +const DEFAULT_CRITERIA = [ + { + blockName: 'Оценка проекта команды', + criteriaType: 'team', + criteria: [ + { name: 'Соответствие решения поставленной задаче', maxScore: 5 }, + { name: 'Оригинальность - использование нестандартных технических и проектных подходов', maxScore: 5 }, + { name: 'Работоспособность решения', maxScore: 1 }, + { name: 'Технологическая сложность решения', maxScore: 2 }, + { name: 'Объем функциональных возможностей решения', maxScore: 2 }, + { name: 'Аргументация способа выбранного решения', maxScore: 5 }, + { name: 'Качество предоставления информации', maxScore: 5 }, + { name: 'Наличие удобного UX/UI', maxScore: 5 }, + { name: 'Наличие не менее 5 AI-агентов', maxScore: 5 } + ], + order: 0 + }, + { + blockName: 'Оценка выступления участника', + criteriaType: 'participant', + criteria: [ + { name: 'Качество презентации и донесения идеи', maxScore: 5 }, + { name: 'Понимание технологии и решения', maxScore: 5 }, + { name: 'Аргументация выбранного подхода', maxScore: 5 }, + { name: 'Ответы на вопросы жюри', maxScore: 5 }, + { name: 'Коммуникативные навыки', maxScore: 5 } + ], + order: 1 + } +]; + +// GET /api/criteria - получить все блоки критериев +router.get('/', async (req, res) => { + try { + const { eventId, criteriaType } = req.query; + const filter = {}; + if (eventId) filter.eventId = eventId; + if (criteriaType && criteriaType !== 'all') { + filter.$or = [ + { criteriaType: criteriaType }, + { criteriaType: 'all' } + ]; + } + const criteria = await Criteria.find(filter).sort({ order: 1 }); + res.json(criteria); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/criteria/:id - получить блок критериев по ID +router.get('/:id', async (req, res) => { + try { + const criteria = await Criteria.findById(req.params.id); + if (!criteria) { + return res.status(404).json({ error: 'Criteria not found' }); + } + res.json(criteria); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/criteria - создать блок критериев +router.post('/', async (req, res) => { + try { + const { eventId, blockName, criteriaType, criteria, order } = req.body; + + if (!eventId || !blockName || !criteria || !Array.isArray(criteria)) { + return res.status(400).json({ error: 'EventId, block name and criteria array are required' }); + } + + const criteriaBlock = await Criteria.create({ + eventId, + blockName, + criteriaType: criteriaType || 'all', + criteria, + order: order !== undefined ? order : 0 + }); + + res.status(201).json(criteriaBlock); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/criteria/default - загрузить критерии по умолчанию из hack.md +router.post('/default', async (req, res) => { + try { + const { eventId } = req.body; + + if (!eventId) { + return res.status(400).json({ error: 'EventId is required' }); + } + + // Удаляем все существующие критерии для этого мероприятия + await Criteria.deleteMany({ eventId }); + + // Создаем критерии по умолчанию с eventId + const criteriaWithEventId = DEFAULT_CRITERIA.map(c => ({ + ...c, + eventId + })); + const createdCriteria = await Criteria.insertMany(criteriaWithEventId); + + res.status(201).json(createdCriteria); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PUT /api/criteria/:id - редактировать блок +router.put('/:id', async (req, res) => { + try { + const { blockName, criteriaType, criteria, order } = req.body; + + const criteriaBlock = await Criteria.findById(req.params.id); + if (!criteriaBlock) { + return res.status(404).json({ error: 'Criteria not found' }); + } + + if (blockName !== undefined) criteriaBlock.blockName = blockName; + if (criteriaType !== undefined) criteriaBlock.criteriaType = criteriaType; + if (criteria !== undefined) criteriaBlock.criteria = criteria; + if (order !== undefined) criteriaBlock.order = order; + + await criteriaBlock.save(); + res.json(criteriaBlock); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /api/criteria/:id - удалить блок +router.delete('/:id', async (req, res) => { + try { + const criteria = await Criteria.findByIdAndDelete(req.params.id); + if (!criteria) { + return res.status(404).json({ error: 'Criteria not found' }); + } + res.json({ message: 'Criteria deleted successfully', criteria }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; + diff --git a/server/routers/assessment-tools/routes/event.js b/server/routers/assessment-tools/routes/event.js new file mode 100644 index 0000000..5fc614d --- /dev/null +++ b/server/routers/assessment-tools/routes/event.js @@ -0,0 +1,108 @@ +const router = require('express').Router(); +const { Event } = require('../models'); + +// GET /api/events - получить все мероприятия +router.get('/', async (req, res) => { + try { + const events = await Event.find().sort({ eventDate: -1 }); + res.json(events); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/events/:id - получить одно мероприятие +router.get('/:id', async (req, res) => { + try { + const event = await Event.findById(req.params.id); + + if (!event) { + return res.status(404).json({ error: 'Мероприятие не найдено' }); + } + + res.json(event); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/events - создать новое мероприятие +router.post('/', async (req, res) => { + try { + const { name, description, eventDate, location, status } = req.body; + + const event = await Event.create({ + name: name || 'Новое мероприятие', + description: description || '', + eventDate: eventDate || new Date(), + location: location || '', + status: status || 'draft', + votingEnabled: false + }); + + res.status(201).json(event); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PUT /api/events/:id - обновить мероприятие +router.put('/:id', async (req, res) => { + try { + const { name, description, eventDate, location, status } = req.body; + + const event = await Event.findById(req.params.id); + + if (!event) { + return res.status(404).json({ error: 'Мероприятие не найдено' }); + } + + if (name !== undefined) event.name = name; + if (description !== undefined) event.description = description; + if (eventDate !== undefined) event.eventDate = eventDate; + if (location !== undefined) event.location = location; + if (status !== undefined) event.status = status; + + await event.save(); + + res.json(event); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /api/events/:id - удалить мероприятие +router.delete('/:id', async (req, res) => { + try { + const event = await Event.findByIdAndDelete(req.params.id); + + if (!event) { + return res.status(404).json({ error: 'Мероприятие не найдено' }); + } + + res.json({ message: 'Мероприятие удалено', event }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/events/:id/toggle-voting - вкл/выкл оценку +router.patch('/:id/toggle-voting', async (req, res) => { + try { + const event = await Event.findById(req.params.id); + + if (!event) { + return res.status(404).json({ error: 'Мероприятие не найдено' }); + } + + event.votingEnabled = !event.votingEnabled; + await event.save(); + + res.json(event); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; + diff --git a/server/routers/assessment-tools/routes/experts.js b/server/routers/assessment-tools/routes/experts.js new file mode 100644 index 0000000..849c45f --- /dev/null +++ b/server/routers/assessment-tools/routes/experts.js @@ -0,0 +1,117 @@ +const router = require('express').Router(); +const { Expert } = require('../models'); + +// GET /api/experts - список экспертов +router.get('/', async (req, res) => { + try { + const { eventId } = req.query; + const filter = {}; + if (eventId) filter.eventId = eventId; + const experts = await Expert.find(filter).sort({ createdAt: -1 }); + res.json(experts); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/experts/by-token/:token - получить данные эксперта по токену +router.get('/by-token/:token', async (req, res) => { + try { + const expert = await Expert.findOne({ token: req.params.token }); + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + res.json(expert); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/experts/:id - получить эксперта по ID +router.get('/:id', async (req, res) => { + try { + const expert = await Expert.findById(req.params.id); + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + res.json(expert); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/experts - создать эксперта (с генерацией уникальной ссылки) +router.post('/', async (req, res) => { + try { + const { eventId, fullName } = req.body; + + if (!eventId || !fullName) { + return res.status(400).json({ error: 'EventId and full name are required' }); + } + + // Создаем нового эксперта + const expert = new Expert({ + eventId, + fullName + }); + + // Сохраняем эксперта (токен генерируется в pre-save хуке) + await expert.save(); + + // Формируем URL для QR кода ПОСЛЕ сохранения, когда токен уже сгенерирован + // Приоритеты: + // 1) Явная переменная окружения FRONTEND_BASE_URL (например, https://platform.brojs.ru) + // 2) Проксируемые заголовки x-forwarded-proto / x-forwarded-host + // 3) Локальные req.protocol + req.get('host') + const forwardedProto = req.get('x-forwarded-proto'); + const forwardedHost = req.get('x-forwarded-host'); + const protocol = forwardedProto || req.protocol; + const host = forwardedHost || req.get('host'); + const baseUrl = process.env.FRONTEND_BASE_URL || `${protocol}://${host}`; + + expert.qrCodeUrl = `${baseUrl}/assessment-tools/expert/${expert.token}`; + + // Сохраняем еще раз с обновленным qrCodeUrl + await expert.save(); + + res.status(201).json(expert); + } catch (error) { + console.error('Error creating expert:', error); + res.status(500).json({ error: error.message }); + } +}); + +// PUT /api/experts/:id - редактировать эксперта +router.put('/:id', async (req, res) => { + try { + const { fullName } = req.body; + + const expert = await Expert.findById(req.params.id); + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + + if (fullName !== undefined) expert.fullName = fullName; + + await expert.save(); + res.json(expert); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /api/experts/:id - удалить эксперта +router.delete('/:id', async (req, res) => { + try { + const expert = await Expert.findByIdAndDelete(req.params.id); + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + res.json({ message: 'Expert deleted successfully', expert }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; + diff --git a/server/routers/assessment-tools/routes/ratings.js b/server/routers/assessment-tools/routes/ratings.js new file mode 100644 index 0000000..f58b899 --- /dev/null +++ b/server/routers/assessment-tools/routes/ratings.js @@ -0,0 +1,240 @@ +const router = require('express').Router(); +const { Rating, Team, Expert, Criteria } = require('../models'); + +// GET /api/ratings - получить все оценки (с фильтрами) +router.get('/', async (req, res) => { + try { + const { expertId, teamId, eventId } = req.query; + const filter = {}; + + if (expertId) filter.expertId = expertId; + if (teamId) filter.teamId = teamId; + if (eventId) filter.eventId = eventId; + + const ratings = await Rating.find(filter) + .populate('expertId', 'fullName') + .populate('teamId', 'name type') + .sort({ createdAt: -1 }); + + res.json(ratings); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/ratings/team/:teamId - оценки конкретной команды +router.get('/team/:teamId', async (req, res) => { + try { + const ratings = await Rating.find({ teamId: req.params.teamId }) + .populate('expertId', 'fullName') + .populate('teamId', 'name type projectName'); + + res.json(ratings); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/ratings/expert/:expertId - оценки конкретного эксперта +router.get('/expert/:expertId', async (req, res) => { + try { + const ratings = await Rating.find({ expertId: req.params.expertId }) + .populate('teamId', 'name type projectName'); + + res.json(ratings); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/ratings/statistics - статистика с группировкой по командам +router.get('/statistics', async (req, res) => { + try { + const { type, eventId } = req.query; + + // Получаем все команды + const teamFilter = { isActive: true }; + if (type) teamFilter.type = type; + if (eventId) teamFilter.eventId = eventId; + const teams = await Team.find(teamFilter); + + // Получаем все оценки + const ratingFilter = {}; + if (eventId) ratingFilter.eventId = eventId; + const ratings = await Rating.find(ratingFilter) + .populate('expertId', 'fullName') + .populate('teamId', 'name type projectName'); + + // Группируем оценки по командам + const statistics = teams.map(team => { + const teamRatings = ratings.filter(r => r.teamId && r.teamId._id.toString() === team._id.toString()); + + // Считаем средние оценки по критериям + const criteriaStats = {}; + teamRatings.forEach(rating => { + rating.ratings.forEach(item => { + if (!criteriaStats[item.criterionName]) { + criteriaStats[item.criterionName] = { + name: item.criterionName, + scores: [], + average: 0 + }; + } + criteriaStats[item.criterionName].scores.push(item.score); + }); + }); + + // Вычисляем средние значения + Object.keys(criteriaStats).forEach(key => { + const scores = criteriaStats[key].scores; + criteriaStats[key].average = scores.reduce((sum, s) => sum + s, 0) / scores.length; + }); + + // Считаем общий балл команды (среднее от всех экспертов) + const totalScore = teamRatings.length > 0 + ? teamRatings.reduce((sum, r) => sum + r.totalScore, 0) / teamRatings.length + : 0; + + return { + team: { + _id: team._id, + name: team.name, + type: team.type, + projectName: team.projectName + }, + ratings: teamRatings.map(r => ({ + expert: r.expertId ? r.expertId.fullName : 'Unknown', + criteria: r.ratings, + totalScore: r.totalScore + })), + criteriaStats: Object.values(criteriaStats), + totalScore: totalScore, + ratingsCount: teamRatings.length + }; + }); + + res.json(statistics); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/ratings/top3 - топ-3 команды и топ-3 участники отдельно +// ВАЖНО: всегда возвращаем объект вида { teams: Top3Item[], participants: Top3Item[] }, +// чтобы фронтенд мог безопасно работать с data.teams / data.participants +router.get('/top3', async (req, res) => { + try { + const { type, eventId } = req.query; + + // Получаем все активные команды/участников + const teamFilter = { isActive: true }; + if (eventId) teamFilter.eventId = eventId; + const teams = await Team.find(teamFilter); + + const ratingFilter = {}; + if (eventId) ratingFilter.eventId = eventId; + const ratings = await Rating.find(ratingFilter).populate('teamId', 'name type projectName'); + + const calculateTop3 = (sourceTeams) => { + const teamScores = sourceTeams.map((team) => { + const teamRatings = ratings.filter( + (r) => r.teamId && r.teamId._id.toString() === team._id.toString() + ); + + const totalScore = + teamRatings.length > 0 + ? teamRatings.reduce((sum, r) => sum + r.totalScore, 0) / teamRatings.length + : 0; + + return { + team: { + _id: team._id, + name: team.name, + type: team.type, + projectName: team.projectName + }, + totalScore, + ratingsCount: teamRatings.length + }; + }); + + return teamScores + .filter((t) => t.ratingsCount > 0) + .sort((a, b) => b.totalScore - a.totalScore) + .slice(0, 3); + }; + + const teamEntities = teams.filter((t) => t.type === 'team'); + const participantEntities = teams.filter((t) => t.type === 'participant'); + + const teamTop3 = calculateTop3(teamEntities); + const participantTop3 = calculateTop3(participantEntities); + + // Параметр type управляет только содержимым, но не форматом ответа + const response = { + teams: !type || type === 'team' ? teamTop3 : [], + participants: !type || type === 'participant' ? participantTop3 : [] + }; + + res.json(response); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/ratings - создать/обновить оценку эксперта +router.post('/', async (req, res) => { + try { + const { eventId, expertId, teamId, ratings } = req.body; + + if (!eventId || !expertId || !teamId || !ratings || !Array.isArray(ratings)) { + return res.status(400).json({ error: 'EventId, expert ID, team ID, and ratings array are required' }); + } + + // Проверяем существование эксперта и команды + const expert = await Expert.findById(expertId); + const team = await Team.findById(teamId); + + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + // Проверяем, активна ли команда + if (!team.isActive) { + return res.status(400).json({ error: 'Team voting is disabled' }); + } + + // Ищем существующую оценку + let rating = await Rating.findOne({ eventId, expertId, teamId }); + + if (rating) { + // Обновляем существующую оценку + rating.ratings = ratings; + await rating.save(); + } else { + // Создаем новую оценку + rating = await Rating.create({ + eventId, + expertId, + teamId, + ratings + }); + } + + // Возвращаем с populate + rating = await Rating.findById(rating._id) + .populate('expertId', 'fullName') + .populate('teamId', 'name type projectName'); + + res.status(rating ? 200 : 201).json(rating); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; + diff --git a/server/routers/assessment-tools/routes/teams.js b/server/routers/assessment-tools/routes/teams.js new file mode 100644 index 0000000..284db0e --- /dev/null +++ b/server/routers/assessment-tools/routes/teams.js @@ -0,0 +1,194 @@ +const router = require('express').Router(); +const { Team } = require('../models'); + +// GET /api/teams - список всех команд +router.get('/', async (req, res) => { + try { + const { type, eventId } = req.query; + const filter = {}; + if (type) filter.type = type; + if (eventId) filter.eventId = eventId; + const teams = await Team.find(filter).sort({ createdAt: -1 }); + res.json(teams); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/teams/active/voting - получить активную для оценки команду (ДОЛЖЕН БЫТЬ ПЕРЕД /:id) +router.get('/active/voting', async (req, res) => { + try { + const { eventId } = req.query; + const filter = { isActiveForVoting: true }; + if (eventId) filter.eventId = eventId; + const team = await Team.findOne(filter); + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/teams/stop-all-voting/global - остановить все оценивания (ДОЛЖЕН БЫТЬ ПЕРЕД /:id) +router.patch('/stop-all-voting/global', async (req, res) => { + try { + const { eventId } = req.body; + // Находим все команды, которые сейчас оцениваются + const filter = { isActiveForVoting: true }; + if (eventId) filter.eventId = eventId; + + const result = await Team.updateMany( + filter, + { + isActiveForVoting: false, + votingStatus: 'evaluated' + } + ); + + res.json({ + message: 'All voting stopped successfully', + modifiedCount: result.modifiedCount + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/teams/:id - получить команду по ID +router.get('/:id', async (req, res) => { + try { + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/teams - создать команду/участника +router.post('/', async (req, res) => { + try { + const { eventId, type, name, projectName, caseDescription } = req.body; + + if (!eventId || !type || !name) { + return res.status(400).json({ error: 'EventId, type and name are required' }); + } + + const team = await Team.create({ + eventId, + type, + name, + projectName: projectName || '', + caseDescription: caseDescription || '', + isActive: true + }); + + res.status(201).json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PUT /api/teams/:id - редактировать команду +router.put('/:id', async (req, res) => { + try { + const { type, name, projectName, caseDescription } = req.body; + + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + if (type !== undefined) team.type = type; + if (name !== undefined) team.name = name; + if (projectName !== undefined) team.projectName = projectName; + if (caseDescription !== undefined) team.caseDescription = caseDescription; + + await team.save(); + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /api/teams/:id - удалить команду +router.delete('/:id', async (req, res) => { + try { + const team = await Team.findByIdAndDelete(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + res.json({ message: 'Team deleted successfully', team }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/teams/:id/activate-for-voting - активировать команду для оценки +router.patch('/:id/activate-for-voting', async (req, res) => { + try { + // Получаем команду для активации + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + // Деактивируем все команды этого мероприятия + const previouslyActive = await Team.findOne({ + isActiveForVoting: true, + eventId: team.eventId + }); + if (previouslyActive) { + previouslyActive.isActiveForVoting = false; + previouslyActive.votingStatus = 'evaluated'; + await previouslyActive.save(); + } + + // Активируем выбранную команду + team.isActiveForVoting = true; + team.votingStatus = 'evaluating'; + await team.save(); + + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/teams/:id/stop-voting - остановить оценивание конкретной команды +router.patch('/:id/stop-voting', async (req, res) => { + try { + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + team.isActiveForVoting = false; + team.votingStatus = 'evaluated'; + await team.save(); + + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/teams/:id/toggle-active - остановить оценку команды +router.patch('/:id/toggle-active', async (req, res) => { + try { + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + team.isActive = !team.isActive; + await team.save(); + + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/server/routers/assessment-tools/scripts/recreate-test-user.js b/server/routers/assessment-tools/scripts/recreate-test-user.js new file mode 100644 index 0000000..6002d66 --- /dev/null +++ b/server/routers/assessment-tools/scripts/recreate-test-user.js @@ -0,0 +1,36 @@ +// Импортировать mongoose из общего модуля (подключение происходит автоматически) +const mongoose = require('../../../utils/mongoose'); +const Event = require('../models/Event'); + +async function recreateTestUser() { + try { + // Проверяем подключение к MongoDB + if (mongoose.connection.readyState !== 1) { + console.log('Waiting for MongoDB connection...'); + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + console.log('Connected to MongoDB'); + + // Создаем тестовое мероприятие если его нет + let event = await Event.findOne(); + if (!event) { + event = await Event.create({ + name: 'Tatar san', + status: 'draft', + votingEnabled: false + }); + console.log('Test event created:', event.name); + } else { + console.log('Event already exists:', event.name); + } + + console.log('Database initialized successfully'); + } catch (error) { + console.error('Error:', error); + process.exit(1); + } +} + +recreateTestUser(); + diff --git a/server/routers/edateam-legacy/index.js b/server/routers/edateam-legacy/index.js deleted file mode 100644 index 0892e93..0000000 --- a/server/routers/edateam-legacy/index.js +++ /dev/null @@ -1,15 +0,0 @@ -const router = require('express').Router(); - -router.get('/recipe-data', (request, response) => { - response.send(require('./json/recipe-data/success.json')) - }) - -router.get('/userpage-data', (req, res)=>{ - res.send(require('./json/userpage-data/success.json')) -}) - -router.get('/homepage-data', (req, res)=>{ - res.send(require('./json/homepage-data/success.json')) -}) - -module.exports = router; \ No newline at end of file diff --git a/server/routers/edateam-legacy/index.ts b/server/routers/edateam-legacy/index.ts new file mode 100644 index 0000000..1e3833b --- /dev/null +++ b/server/routers/edateam-legacy/index.ts @@ -0,0 +1,21 @@ +import { Router } from 'express'; + +import recipeData from './json/recipe-data/success.json'; +import userpageData from './json/userpage-data/success.json'; +import homepageData from './json/homepage-data/success.json'; + +const router = Router(); + +router.get('/recipe-data', (request, response) => { + response.send(recipeData) + }) + +router.get('/userpage-data', (req, res)=>{ + res.send(userpageData) +}) + +router.get('/homepage-data', (req, res)=>{ + res.send(homepageData) +}) + +export default router; diff --git a/server/routers/kfu-m-24-1/back-new/.env b/server/routers/kfu-m-24-1/back-new/.env new file mode 100644 index 0000000..c7e941b --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/.env @@ -0,0 +1 @@ +GIGACHAT_API_KEY=NzgzNTkxMjMtNDQ0Ny00ODFhLTkwMjgtODYxZjUzYjI0ZWQxOjA5NDEwMzEwLTM5YjItNDUzOS1hYWYzLWE4ZDA1MDExNmQ4Nw== diff --git a/server/routers/kfu-m-24-1/back-new/.gitignore b/server/routers/kfu-m-24-1/back-new/.gitignore new file mode 100644 index 0000000..9439df7 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.env \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/README.md b/server/routers/kfu-m-24-1/back-new/README.md new file mode 100644 index 0000000..97811d1 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/README.md @@ -0,0 +1,21 @@ +# back-new + +非Python实现的后端(Node.js + Express) + +## 启动方法 + +1. 安装依赖: + ```bash + npm install + ``` +2. 启动服务: + ```bash + npm start + ``` + +默认端口:`3002` + +## 支持接口 +- POST `/api/auth/login` 用户登录 +- POST `/api/auth/register` 用户注册 +- GET `/gigachat/prompt?prompt=xxx` 生成图片(返回模拟图片链接) \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/app.js b/server/routers/kfu-m-24-1/back-new/app.js new file mode 100644 index 0000000..e87fbc8 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/app.js @@ -0,0 +1,24 @@ +const express = require('express'); +const cors = require('cors'); +const featuresConfig = require('./features.config'); +const imageRoutes = require('./features/image/image.routes'); + +const app = express(); +app.use(cors()); +app.use(express.json()); + +if (featuresConfig.auth) { + app.use('/api/auth', require('./features/auth/auth.routes')); +} +if (featuresConfig.user) { + app.use('/api/user', require('./features/user/user.routes')); +} +if (featuresConfig.image) { + app.use('/gigachat', imageRoutes); +} + +app.get('/api/', (req, res) => { + res.json({ message: 'API root' }); +}); + +module.exports = app; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/features.config.js b/server/routers/kfu-m-24-1/back-new/features.config.js new file mode 100644 index 0000000..7a69e24 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/features.config.js @@ -0,0 +1,5 @@ +module.exports = { + auth: true, + user: true, + image: true, // 关闭为 false +}; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/features/auth/auth.controller.js b/server/routers/kfu-m-24-1/back-new/features/auth/auth.controller.js new file mode 100644 index 0000000..21c400f --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/features/auth/auth.controller.js @@ -0,0 +1,95 @@ +const usersDb = require('../../shared/usersDb'); +const makeLinks = require('../../shared/hateoas'); + +exports.login = (req, res) => { + const { username, password, email } = req.body; + const user = usersDb.findUser(username, email, password); + if (user) { + res.json({ + data: { + user: { + id: user.id, + username: user.username, + email: user.email, + firstName: user.firstName, + lastName: user.lastName + }, + token: 'token-' + user.id, + message: 'Login successful' + }, + _links: makeLinks('/api/auth', { + self: '/login', + profile: '/profile/', + logout: '/logout' + }), + _meta: {} + }); + } else { + res.status(401).json({ error: 'Invalid credentials' }); + } +}; + +exports.register = (req, res) => { + const { username, password, email, firstName, lastName } = req.body; + if (usersDb.exists(username, email)) { + return res.status(409).json({ error: 'User already exists' }); + } + const newUser = usersDb.addUser({ username, password, email, firstName, lastName }); + res.json({ + data: { + user: { + id: newUser.id, + username, + email, + firstName, + lastName + }, + token: 'token-' + newUser.id, + message: 'Register successful' + }, + _links: makeLinks('/api/auth', { + self: '/register', + login: '/login', + profile: '/profile/' + }), + _meta: {} + }); +}; + +exports.profile = (req, res) => { + const auth = req.headers.authorization; + if (!auth || !auth.startsWith('Bearer ')) { + return res.status(401).json({ error: 'No token provided' }); + } + const token = auth.replace('Bearer ', ''); + const id = parseInt(token.replace('token-', '')); + const user = usersDb.findById(id); + if (!user) { + return res.status(401).json({ error: 'Invalid token' }); + } + res.json({ + data: { + id: user.id, + username: user.username, + email: user.email, + firstName: user.firstName, + lastName: user.lastName + }, + _links: makeLinks('/api/auth', { + self: '/profile/', + logout: '/logout' + }), + _meta: {} + }); +}; + +exports.logout = (req, res) => { + res.json({ + message: 'Logout successful', + _links: makeLinks('/api/auth', { + self: '/logout', + login: '/login' + }), + _meta: {} + }); +}; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/features/auth/auth.routes.js b/server/routers/kfu-m-24-1/back-new/features/auth/auth.routes.js new file mode 100644 index 0000000..d983771 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/features/auth/auth.routes.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); +const ctrl = require('./auth.controller'); + +router.post('/login', ctrl.login); +router.post('/register', ctrl.register); +router.get('/profile/', ctrl.profile); +router.post('/logout', ctrl.logout); + +module.exports = router; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/features/image/image.controller.js b/server/routers/kfu-m-24-1/back-new/features/image/image.controller.js new file mode 100644 index 0000000..f7b2451 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/features/image/image.controller.js @@ -0,0 +1,82 @@ +const axios = require('axios'); +const makeLinks = require('../../shared/hateoas'); +const path = require('path'); +const qs = require('qs'); +const { v4: uuidv4 } = require('uuid'); +require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + +exports.generate = async (req, res) => { + const { prompt } = req.query; + if (!prompt) { + return res.status(400).json({ error: 'Prompt parameter is required' }); + } + try { + const apiKey = process.env.GIGACHAT_API_KEY; + const tokenResp = await axios.post( + 'https://ngw.devices.sberbank.ru:9443/api/v2/oauth', + { + 'scope':' GIGACHAT_API_PERS', + }, + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json', + 'Authorization': `Basic ${apiKey}`, + 'RqUID':'6f0b1291-c7f3-43c6-bb2e-9f3efb2dc98e' + }, + } + ); + const accessToken = tokenResp.data.access_token; + const chatResp = await axios.post( + 'https://gigachat.devices.sberbank.ru/api/v1/chat/completions', + { + model: "GigaChat", + messages: [ + { role: "system", content: "Ты — Василий Кандинский" }, + { role: "user", content: prompt } + ], + stream: false, + function_call: 'auto' + }, + { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'RqUID': uuidv4(), + } + } + ); + const content = chatResp.data.choices[0].message.content; + // eslint-disable-next-line no-useless-escape + const match = content.match(/ { + res.json({ + data: usersDb.getAll(), + _links: makeLinks('/api/user', { + self: '/list', + }), + _meta: {} + }); +}; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/features/user/user.routes.js b/server/routers/kfu-m-24-1/back-new/features/user/user.routes.js new file mode 100644 index 0000000..f444cad --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/features/user/user.routes.js @@ -0,0 +1,7 @@ +const express = require('express'); +const router = express.Router(); +const ctrl = require('./user.controller'); + +router.get('/list', ctrl.list); + +module.exports = router; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/package-lock.json b/server/routers/kfu-m-24-1/back-new/package-lock.json new file mode 100644 index 0000000..f8e2608 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/package-lock.json @@ -0,0 +1,5455 @@ +{ + "name": "back-new", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "back-new", + "version": "1.0.0", + "dependencies": { + "axios": "^1.10.0", + "cors": "^2.8.5", + "dotenv": "^17.0.0", + "express": "^4.21.2", + "qs": "^6.14.0", + "uuid": "^11.1.0" + }, + "devDependencies": { + "jest": "^30.0.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@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", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "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/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@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", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.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.npmmirror.com/@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" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.7" + }, + "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.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@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" + }, + "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.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@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" + }, + "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.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@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" + }, + "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.npmmirror.com/@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" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@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", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.4.3.tgz", + "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", + "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@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/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@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", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/@jest/console/-/console-30.0.2.tgz", + "integrity": "sha512-krGElPU0FipAqpVZ/BRZOy0MZh/ARdJ0Nj+PiH1ykFY1+VpBlYNLjdjVA5CFKxnKR6PFqFutO4Z7cdK9BlGiDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.0.2", + "jest-util": "30.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/@jest/core/-/core-30.0.3.tgz", + "integrity": "sha512-Mgs1N+NSHD3Fusl7bOq1jyxv1JDAUwjy+0DhVR93Q6xcBP9/bAQ+oZhXb5TTnP5sQzAHgb7ROCKQ2SnovtxYtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.0.2", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.0.2", + "@jest/test-result": "30.0.2", + "@jest/transform": "30.0.2", + "@jest/types": "30.0.1", + "@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.0.2", + "jest-config": "30.0.3", + "jest-haste-map": "30.0.2", + "jest-message-util": "30.0.2", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.2", + "jest-resolve-dependencies": "30.0.3", + "jest-runner": "30.0.3", + "jest-runtime": "30.0.3", + "jest-snapshot": "30.0.3", + "jest-util": "30.0.2", + "jest-validate": "30.0.2", + "jest-watcher": "30.0.2", + "micromatch": "^4.0.8", + "pretty-format": "30.0.2", + "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" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmmirror.com/@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" + } + }, + "node_modules/@jest/environment": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/@jest/environment/-/environment-30.0.2.tgz", + "integrity": "sha512-hRLhZRJNxBiOhxIKSq2UkrlhMt3/zVFQOAi5lvS8T9I03+kxsbflwHJEF+eXEYXCrRGRhHwECT7CDk6DyngsRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.0.2", + "@jest/types": "30.0.1", + "@types/node": "*", + "jest-mock": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/@jest/expect/-/expect-30.0.3.tgz", + "integrity": "sha512-73BVLqfCeWjYWPEQoYjiRZ4xuQRhQZU0WdgvbyXGRHItKQqg5e6mt2y1kVhzLSuZpmUnccZHbGynoaL7IcLU3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.0.3", + "jest-snapshot": "30.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/@jest/expect-utils/-/expect-utils-30.0.3.tgz", + "integrity": "sha512-SMtBvf2sfX2agcT0dA9pXwcUrKvOSDqBY4e4iRfT+Hya33XzV35YVg+98YQFErVGA/VR1Gto5Y2+A6G9LSQ3Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/@jest/fake-timers/-/fake-timers-30.0.2.tgz", + "integrity": "sha512-jfx0Xg7l0gmphTY9UKm5RtH12BlLYj/2Plj6wXjVW5Era4FZKfXeIvwC67WX+4q8UCFxYS20IgnMcFBcEU0DtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.0.2", + "jest-mock": "30.0.2", + "jest-util": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmmirror.com/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/@jest/globals/-/globals-30.0.3.tgz", + "integrity": "sha512-fIduqNyYpMeeSr5iEAiMn15KxCzvrmxl7X7VwLDRGj7t5CoHtbF+7K3EvKk32mOUIJ4kIvFRlaixClMH2h/Vaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.0.2", + "@jest/expect": "30.0.3", + "@jest/types": "30.0.1", + "jest-mock": "30.0.2" + }, + "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.npmmirror.com/@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" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/@jest/reporters/-/reporters-30.0.2.tgz", + "integrity": "sha512-l4QzS/oKf57F8WtPZK+vvF4Io6ukplc6XgNFu4Hd/QxaLEO9f+8dSFzUua62Oe0HKlCUjKHpltKErAgDiMJKsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.0.2", + "@jest/test-result": "30.0.2", + "@jest/transform": "30.0.2", + "@jest/types": "30.0.1", + "@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.0.2", + "jest-util": "30.0.2", + "jest-worker": "30.0.2", + "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.1", + "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-30.0.1.tgz", + "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "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.0.1", + "resolved": "https://registry.npmmirror.com/@jest/snapshot-utils/-/snapshot-utils-30.0.1.tgz", + "integrity": "sha512-6Dpv7vdtoRiISEFwYF8/c7LIvqXD7xDXtLPNzC2xqAfBznKip0MQM+rkseKwUPUpv2PJ7KW/YsnwWXrIL2xF+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "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", + "resolved": "https://registry.npmmirror.com/@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", + "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.0.2", + "resolved": "https://registry.npmmirror.com/@jest/test-result/-/test-result-30.0.2.tgz", + "integrity": "sha512-KKMuBKkkZYP/GfHMhI+cH2/P3+taMZS3qnqqiPC1UXZTJskkCS+YU/ILCtw5anw1+YsTulDHFpDo70mmCedW8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.0.2", + "@jest/types": "30.0.1", + "@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.0.2", + "resolved": "https://registry.npmmirror.com/@jest/test-sequencer/-/test-sequencer-30.0.2.tgz", + "integrity": "sha512-fbyU5HPka0rkalZ3MXVvq0hwZY8dx3Y6SCqR64zRmh+xXlDeFl0IdL4l9e7vp4gxEXTYHbwLFA1D+WW5CucaSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.0.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/@jest/transform/-/transform-30.0.2.tgz", + "integrity": "sha512-kJIuhLMTxRF7sc0gPzPtCDib/V9KwW3I2U25b+lYCYMVqHHSrcZopS8J8H+znx9yixuFv+Iozl8raLt/4MoxrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.0.1", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.0", + "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.0.2", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.2", + "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.0.1", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-30.0.1.tgz", + "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.1", + "@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.10", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", + "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", + "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.27", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", + "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.37", + "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmmirror.com/@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" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@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", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@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" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "24.0.7", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.7.tgz", + "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@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.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", + "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", + "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", + "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", + "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", + "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", + "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", + "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", + "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", + "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", + "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", + "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", + "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", + "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", + "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", + "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", + "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", + "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.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", + "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", + "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", + "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/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/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/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" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/babel-jest/-/babel-jest-30.0.2.tgz", + "integrity": "sha512-A5kqR1/EUTidM2YC2YMEUDP2+19ppgOwK0IAd9Swc3q2KqFb5f9PtRUXVeZcngu0z5mDMyZ9zH2huJZSOMLiTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.0.2", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.0", + "babel-preset-jest": "30.0.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", + "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.0.1", + "resolved": "https://registry.npmmirror.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", + "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.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" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.0.1", + "resolved": "https://registry.npmmirror.com/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", + "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.0.1", + "babel-preset-current-node-syntax": "^1.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "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" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.2.0.tgz", + "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", + "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/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", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/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", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/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", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/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", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/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" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "17.0.0", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.0.0.tgz", + "integrity": "sha512-A0BJ5lrpJVSfnMMXjmeO0xUnoxqsBHWCoqqTnGwGYVdnctqXXUEhJOO7LxmgxJon9tEZFGpe0xPRX0h2v3AANQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "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.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.177", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "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.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/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", + "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/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/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" + } + }, + "node_modules/expect": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/expect/-/expect-30.0.3.tgz", + "integrity": "sha512-HXg6NvK35/cSYZCUKAtmlgCFyqKM4frEPbzrav5hRqb0GMz0E0lS5hfzYjSaiaE5ysnp/qI2aeZkeyeIAOeXzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.0.3", + "@jest/get-type": "30.0.1", + "jest-matcher-utils": "30.0.3", + "jest-message-util": "30.0.2", + "jest-mock": "30.0.2", + "jest-util": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.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/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/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/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "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/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "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.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/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", + "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.npmmirror.com/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmmirror.com/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", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/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/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/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" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/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" + }, + "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.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/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.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/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" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/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", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/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", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmmirror.com/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", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest/-/jest-30.0.3.tgz", + "integrity": "sha512-Uy8xfeE/WpT2ZLGDXQmaYNzw2v8NUKuYeKGtkS6sDxwsdQihdgYCXaKIYnph1h95DN5H35ubFDm0dfmsQnjn4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.0.3", + "@jest/types": "30.0.1", + "import-local": "^3.2.0", + "jest-cli": "30.0.3" + }, + "bin": { + "jest": "bin/jest.js" + }, + "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-changed-files": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-changed-files/-/jest-changed-files-30.0.2.tgz", + "integrity": "sha512-Ius/iRST9FKfJI+I+kpiDh8JuUlAISnRszF9ixZDIqJF17FckH5sOzKC8a0wd0+D+8em5ADRHA5V5MnfeDk2WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.0.2", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-circus/-/jest-circus-30.0.3.tgz", + "integrity": "sha512-rD9qq2V28OASJHJWDRVdhoBdRs6k3u3EmBzDYcyuMby8XCO3Ll1uq9kyqM41ZcC4fMiPulMVh3qMw0cBvDbnyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.0.2", + "@jest/expect": "30.0.3", + "@jest/test-result": "30.0.2", + "@jest/types": "30.0.1", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.0.2", + "jest-matcher-utils": "30.0.3", + "jest-message-util": "30.0.2", + "jest-runtime": "30.0.3", + "jest-snapshot": "30.0.3", + "jest-util": "30.0.2", + "p-limit": "^3.1.0", + "pretty-format": "30.0.2", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-cli/-/jest-cli-30.0.3.tgz", + "integrity": "sha512-UWDSj0ayhumEAxpYRlqQLrssEi29kdQ+kddP94AuHhZknrE+mT0cR0J+zMHKFe9XPfX3dKQOc2TfWki3WhFTsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.0.3", + "@jest/test-result": "30.0.2", + "@jest/types": "30.0.1", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.0.3", + "jest-util": "30.0.2", + "jest-validate": "30.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "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-config": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-config/-/jest-config-30.0.3.tgz", + "integrity": "sha512-j0L4oRCtJwNyZktXIqwzEiDVQXBbQ4dqXuLD/TZdn++hXIcIfZmjHgrViEy5s/+j4HvITmAXbexVZpQ/jnr0bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.0.1", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.0.2", + "@jest/types": "30.0.1", + "babel-jest": "30.0.2", + "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.0.3", + "jest-docblock": "30.0.1", + "jest-environment-node": "30.0.2", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.2", + "jest-runner": "30.0.3", + "jest-util": "30.0.2", + "jest-validate": "30.0.2", + "micromatch": "^4.0.8", + "parse-json": "^5.2.0", + "pretty-format": "30.0.2", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "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": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-diff/-/jest-diff-30.0.3.tgz", + "integrity": "sha512-Q1TAV0cUcBTic57SVnk/mug0/ASyAqtSIOkr7RAlxx97llRYsM74+E8N5WdGJUlwCKwgxPAkVjKh653h1+HA9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "pretty-format": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.0.1", + "resolved": "https://registry.npmmirror.com/jest-docblock/-/jest-docblock-30.0.1.tgz", + "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-each/-/jest-each-30.0.2.tgz", + "integrity": "sha512-ZFRsTpe5FUWFQ9cWTMguCaiA6kkW5whccPy9JjD1ezxh+mJeqmz8naL8Fl/oSbNJv3rgB0x87WBIkA5CObIUZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1", + "@jest/types": "30.0.1", + "chalk": "^4.1.2", + "jest-util": "30.0.2", + "pretty-format": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-30.0.2.tgz", + "integrity": "sha512-XsGtZ0H+a70RsxAQkKuIh0D3ZlASXdZdhpOSBq9WRPq6lhe0IoQHGW0w9ZUaPiZQ/CpkIdprvlfV1QcXcvIQLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.0.2", + "@jest/fake-timers": "30.0.2", + "@jest/types": "30.0.1", + "@types/node": "*", + "jest-mock": "30.0.2", + "jest-util": "30.0.2", + "jest-validate": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-haste-map/-/jest-haste-map-30.0.2.tgz", + "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.2", + "jest-worker": "30.0.2", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-leak-detector/-/jest-leak-detector-30.0.2.tgz", + "integrity": "sha512-U66sRrAYdALq+2qtKffBLDWsQ/XoNNs2Lcr83sc9lvE/hEpNafJlq2lXCPUBMNqamMECNxSIekLfe69qg4KMIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1", + "pretty-format": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-matcher-utils/-/jest-matcher-utils-30.0.3.tgz", + "integrity": "sha512-hMpVFGFOhYmIIRGJ0HgM9htC5qUiJ00famcc9sRFchJJiLZbbVKrAztcgE6VnXLRxA3XZ0bvNA7hQWh3oHXo/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "jest-diff": "30.0.3", + "pretty-format": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-30.0.2.tgz", + "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-mock/-/jest-mock-30.0.2.tgz", + "integrity": "sha512-PnZOHmqup/9cT/y+pXIVbbi8ID6U1XHRmbvR7MvUy4SLqhCbwpkmXhLbsWbGewHrV5x/1bF7YDjs+x24/QSvFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "@types/node": "*", + "jest-util": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmmirror.com/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" + } + }, + "node_modules/jest-resolve": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-resolve/-/jest-resolve-30.0.2.tgz", + "integrity": "sha512-q/XT0XQvRemykZsvRopbG6FQUT6/ra+XV6rPijyjT6D0msOyCvR2A5PlWZLd+fH0U8XWKZfDiAgrUNDNX2BkCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.2", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.0.2", + "jest-validate": "30.0.2", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.3.tgz", + "integrity": "sha512-FlL6u7LiHbF0Oe27k7DHYMq2T2aNpPhxnNo75F7lEtu4A6sSw+TKkNNUGNcVckdFoL0RCWREJsC1HsKDwKRZzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-runner/-/jest-runner-30.0.3.tgz", + "integrity": "sha512-CxYBzu9WStOBBXAKkLXGoUtNOWsiS1RRmUQb6SsdUdTcqVncOau7m8AJ4cW3Mz+YL1O9pOGPSYLyvl8HBdFmkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.0.2", + "@jest/environment": "30.0.2", + "@jest/test-result": "30.0.2", + "@jest/transform": "30.0.2", + "@jest/types": "30.0.1", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.0.1", + "jest-environment-node": "30.0.2", + "jest-haste-map": "30.0.2", + "jest-leak-detector": "30.0.2", + "jest-message-util": "30.0.2", + "jest-resolve": "30.0.2", + "jest-runtime": "30.0.3", + "jest-util": "30.0.2", + "jest-watcher": "30.0.2", + "jest-worker": "30.0.2", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-runtime/-/jest-runtime-30.0.3.tgz", + "integrity": "sha512-Xjosq0C48G9XEQOtmgrjXJwPaUPaq3sPJwHDRaiC+5wi4ZWxO6Lx6jNkizK/0JmTulVNuxP8iYwt77LGnfg3/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.0.2", + "@jest/fake-timers": "30.0.2", + "@jest/globals": "30.0.3", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.0.2", + "@jest/transform": "30.0.2", + "@jest/types": "30.0.1", + "@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.0.2", + "jest-message-util": "30.0.2", + "jest-mock": "30.0.2", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.2", + "jest-snapshot": "30.0.3", + "jest-util": "30.0.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.0.3", + "resolved": "https://registry.npmmirror.com/jest-snapshot/-/jest-snapshot-30.0.3.tgz", + "integrity": "sha512-F05JCohd3OA1N9+5aEPXA6I0qOfZDGIx0zTq5Z4yMBg2i1p5ELfBusjYAWwTkC12c7dHcbyth4QAfQbS7cRjow==", + "dev": true, + "license": "MIT", + "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.0.3", + "@jest/get-type": "30.0.1", + "@jest/snapshot-utils": "30.0.1", + "@jest/transform": "30.0.2", + "@jest/types": "30.0.1", + "babel-preset-current-node-syntax": "^1.1.0", + "chalk": "^4.1.2", + "expect": "30.0.3", + "graceful-fs": "^4.2.11", + "jest-diff": "30.0.3", + "jest-matcher-utils": "30.0.3", + "jest-message-util": "30.0.2", + "jest-util": "30.0.2", + "pretty-format": "30.0.2", + "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-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-30.0.2.tgz", + "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-validate/-/jest-validate-30.0.2.tgz", + "integrity": "sha512-noOvul+SFER4RIvNAwGn6nmV2fXqBq67j+hKGHKGFCmK4ks/Iy1FSrqQNBLGKlu4ZZIRL6Kg1U72N1nxuRCrGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1", + "@jest/types": "30.0.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-watcher/-/jest-watcher-30.0.2.tgz", + "integrity": "sha512-vYO5+E7jJuF+XmONr6CrbXdlYrgvZqtkn6pdkgjt/dU64UAdc0v1cAVaAeWtAfUUMScxNmnUjKPUMdCpNVASwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.0.2", + "@jest/types": "30.0.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.0.2", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-30.0.2.tgz", + "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.0.2", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/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" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/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/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/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/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/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/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/napi-postinstall": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/napi-postinstall/-/napi-postinstall-0.2.5.tgz", + "integrity": "sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==", + "dev": true, + "license": "MIT", + "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.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/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/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/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", + "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.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/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" + }, + "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.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "30.0.2", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-30.0.2.tgz", + "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.1", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/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" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/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" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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", + "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.npmmirror.com/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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/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", + "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.npmmirror.com/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", + "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": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/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", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/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", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/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/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.8", + "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.8.tgz", + "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/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", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/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/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/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", + "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.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmmirror.com/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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/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/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.9.2", + "resolved": "https://registry.npmmirror.com/unrs-resolver/-/unrs-resolver-1.9.2.tgz", + "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.2.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.9.2", + "@unrs/resolver-binding-android-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-x64": "1.9.2", + "@unrs/resolver-binding-freebsd-x64": "1.9.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-musl": "1.9.2", + "@unrs/resolver-binding-wasm32-wasi": "1.9.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/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", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/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", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/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", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/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/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/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", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/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" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/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", + "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.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/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", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/server/routers/kfu-m-24-1/back-new/package.json b/server/routers/kfu-m-24-1/back-new/package.json new file mode 100644 index 0000000..df84ae0 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/package.json @@ -0,0 +1,21 @@ +{ + "name": "back-new", + "version": "1.0.0", + "description": "非Python实现的后端,兼容前端接口", + "main": "server.js", + "scripts": { + "start": "node server.js", + "test": "jest" + }, + "dependencies": { + "axios": "^1.10.0", + "cors": "^2.8.5", + "dotenv": "^17.0.0", + "express": "^4.21.2", + "qs": "^6.14.0", + "uuid": "^11.1.0" + }, + "devDependencies": { + "jest": "^30.0.3" + } +} diff --git a/server/routers/kfu-m-24-1/back-new/server.js b/server/routers/kfu-m-24-1/back-new/server.js new file mode 100644 index 0000000..8bdfa2d --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/server.js @@ -0,0 +1,5 @@ +const app = require('./app'); +const PORT = process.env.PORT || 3002; +app.listen(PORT, () => { + console.log(`Mock backend running on https://dev.bro.js.ru/ms/back-new/${PORT}`); +}); \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/shared/hateoas.js b/server/routers/kfu-m-24-1/back-new/shared/hateoas.js new file mode 100644 index 0000000..b12ec23 --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/shared/hateoas.js @@ -0,0 +1,8 @@ +function makeLinks(base, links) { + const result = {}; + for (const [rel, path] of Object.entries(links)) { + result[rel] = { href: base + path }; + } + return result; +} +module.exports = makeLinks; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/back-new/shared/usersDb.js b/server/routers/kfu-m-24-1/back-new/shared/usersDb.js new file mode 100644 index 0000000..0b888ef --- /dev/null +++ b/server/routers/kfu-m-24-1/back-new/shared/usersDb.js @@ -0,0 +1,20 @@ +let users = [ + { id: 1, username: 'test', password: '123456', email: 'test@example.com', firstName: 'Test', lastName: 'User' } +]; +let nextId = 2; + +exports.findUser = (username, email, password) => + users.find(u => (u.username === username || u.email === email) && u.password === password); + +exports.findById = (id) => users.find(u => u.id === id); + +exports.addUser = ({ username, password, email, firstName, lastName }) => { + const newUser = { id: nextId++, username, password, email, firstName, lastName }; + users.push(newUser); + return newUser; +}; + +exports.exists = (username, email) => + users.some(u => u.username === username || u.email === email); + +exports.getAll = () => users; \ No newline at end of file diff --git a/server/routers/procurement/index.js b/server/routers/procurement/index.js new file mode 100644 index 0000000..c912fa9 --- /dev/null +++ b/server/routers/procurement/index.js @@ -0,0 +1,113 @@ +const express = require('express'); +const cors = require('cors'); +const dotenv = require('dotenv'); +const fs = require('fs'); + +// Импортировать mongoose из общего модуля (подключение происходит в server/utils/mongoose.ts) +const mongoose = require('../../utils/mongoose'); + +// Загрузить переменные окружения +dotenv.config(); + +// Включить логирование при разработке: установите DEV=true в .env или при запуске +// export DEV=true && npm start (для Linux/Mac) +// set DEV=true && npm start (для Windows) +// По умолчанию логи отключены. Все console.log функции отключаются если DEV !== 'true' +if (process.env.DEV === 'true') { + console.log('ℹ️ DEBUG MODE ENABLED - All logs are visible'); +} + +// Импортировать маршруты - прямые пути без path.join и __dirname +const authRoutes = require('./routes/auth'); +const companiesRoutes = require('./routes/companies'); +const messagesRoutes = require('./routes/messages'); +const searchRoutes = require('./routes/search'); +const buyRoutes = require('./routes/buy'); +const experienceRoutes = require('./routes/experience'); +const productsRoutes = require('./routes/products'); +const reviewsRoutes = require('./routes/reviews'); +const buyProductsRoutes = require('./routes/buyProducts'); +const requestsRoutes = require('./routes/requests'); +const homeRoutes = require('./routes/home'); +const activityRoutes = require('./routes/activity'); + +const app = express(); + +// Проверить подключение к MongoDB (подключение происходит в server/utils/mongoose.ts) +const dbConnected = mongoose.connection.readyState === 1; + +// Middleware +app.use(cors()); +app.use(express.json({ charset: 'utf-8' })); +app.use(express.urlencoded({ extended: true, charset: 'utf-8' })); + +// Set UTF-8 encoding for all responses +app.use((req, res, next) => { + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + next(); +}); + +// CORS headers +app.use((req, res, next) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + if (req.method === 'OPTIONS') { + res.sendStatus(200); + } else { + next(); + } +}); + +// Задержка для имитации сети (опционально) +const delay = (ms = 300) => (req, res, next) => setTimeout(next, ms); +app.use(delay()); + +// Статика для загруженных файлов +const uploadsRoot = 'server/remote-assets/uploads'; +if (!fs.existsSync(uploadsRoot)) { + fs.mkdirSync(uploadsRoot, { recursive: true }); +} +app.use('/uploads', express.static(uploadsRoot)); + +// Health check endpoint +app.get('/health', (req, res) => { + res.json({ + status: 'ok', + api: 'running', + database: dbConnected ? 'mongodb' : 'mock', + timestamp: new Date().toISOString() + }); +}); + +// Маршруты +app.use('/auth', authRoutes); +app.use('/companies', companiesRoutes); +app.use('/messages', messagesRoutes); +app.use('/search', searchRoutes); +app.use('/buy', buyRoutes); +app.use('/buy-products', buyProductsRoutes); +app.use('/experience', experienceRoutes); +app.use('/products', productsRoutes); +app.use('/reviews', reviewsRoutes); +app.use('/requests', requestsRoutes); +app.use('/home', homeRoutes); +app.use('/activities', activityRoutes); + +// Обработка ошибок +app.use((err, req, res, next) => { + console.error('API Error:', err); + res.status(err.status || 500).json({ + error: err.message || 'Internal server error' + }); +}); + +// 404 handler +app.use((req, res) => { + res.status(404).json({ + error: 'Not found' + }); +}); + +// Экспортировать для использования в brojs +module.exports = app; \ No newline at end of file diff --git a/server/routers/procurement/middleware/auth.js b/server/routers/procurement/middleware/auth.js new file mode 100644 index 0000000..da6b4c1 --- /dev/null +++ b/server/routers/procurement/middleware/auth.js @@ -0,0 +1,42 @@ +const jwt = require('jsonwebtoken'); + +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +const verifyToken = (req, res, next) => { + const token = req.headers.authorization?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ error: 'No token provided' }); + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key'); + req.userId = decoded.userId; + req.companyId = decoded.companyId; + req.user = decoded; + log('[Auth] Token verified - userId:', decoded.userId, 'companyId:', decoded.companyId); + next(); + } catch (error) { + console.error('[Auth] Token verification failed:', error.message); + return res.status(401).json({ error: 'Invalid token' }); + } +}; + +const generateToken = (userId, companyId, firstName = '', lastName = '', companyName = '') => { + log('[Auth] Generating token for userId:', userId, 'companyId:', companyId); + return jwt.sign( + { userId, companyId, firstName, lastName, companyName }, + process.env.JWT_SECRET || 'your-secret-key', + { expiresIn: '7d' } + ); +}; + +module.exports = { verifyToken, generateToken }; diff --git a/server/routers/procurement/models/Activity.js b/server/routers/procurement/models/Activity.js new file mode 100644 index 0000000..1b94578 --- /dev/null +++ b/server/routers/procurement/models/Activity.js @@ -0,0 +1,61 @@ +const mongoose = require('mongoose'); + +const activitySchema = new mongoose.Schema({ + companyId: { + type: String, + required: true, + index: true + }, + userId: { + type: String, + required: true + }, + type: { + type: String, + enum: [ + 'message_received', + 'message_sent', + 'request_received', + 'request_sent', + 'request_response', + 'product_accepted', + 'review_received', + 'profile_updated', + 'product_added', + 'buy_product_added' + ], + required: true + }, + title: { + type: String, + required: true + }, + description: { + type: String + }, + relatedCompanyId: { + type: String + }, + relatedCompanyName: { + type: String + }, + metadata: { + type: mongoose.Schema.Types.Mixed + }, + read: { + type: Boolean, + default: false + }, + createdAt: { + type: Date, + default: Date.now, + index: true + } +}); + +// Индексы для оптимизации +activitySchema.index({ companyId: 1, createdAt: -1 }); +activitySchema.index({ companyId: 1, read: 1, createdAt: -1 }); + +module.exports = mongoose.model('Activity', activitySchema); + diff --git a/server/routers/procurement/models/BuyDocument.js b/server/routers/procurement/models/BuyDocument.js new file mode 100644 index 0000000..34d2661 --- /dev/null +++ b/server/routers/procurement/models/BuyDocument.js @@ -0,0 +1,43 @@ +const mongoose = require('mongoose'); + +const buyDocumentSchema = new mongoose.Schema({ + id: { + type: String, + required: true, + unique: true, + index: true + }, + ownerCompanyId: { + type: String, + required: true, + index: true + }, + name: { + type: String, + required: true + }, + type: { + type: String, + required: true + }, + size: { + type: Number, + required: true + }, + filePath: { + type: String, + required: true + }, + acceptedBy: { + type: [String], + default: [] + }, + createdAt: { + type: Date, + default: Date.now, + index: true + } +}); + +module.exports = mongoose.model('BuyDocument', buyDocumentSchema); + diff --git a/server/routers/procurement/models/BuyProduct.js b/server/routers/procurement/models/BuyProduct.js new file mode 100644 index 0000000..24ee7e0 --- /dev/null +++ b/server/routers/procurement/models/BuyProduct.js @@ -0,0 +1,87 @@ +const mongoose = require('mongoose'); + +// Явно определяем схему для файлов +const fileSchema = new mongoose.Schema({ + id: { + type: String, + required: true + }, + name: { + type: String, + required: true + }, + url: { + type: String, + required: true + }, + type: { + type: String, + required: true + }, + size: { + type: Number, + required: true + }, + storagePath: String, + uploadedAt: { + type: Date, + default: Date.now + } +}, { _id: false }); + +const buyProductSchema = new mongoose.Schema({ + companyId: { + type: String, + required: true, + index: true + }, + name: { + type: String, + required: true + }, + description: { + type: String, + required: true, + minlength: 10, + maxlength: 1000 + }, + quantity: { + type: String, + required: true + }, + unit: { + type: String, + default: 'шт' + }, + files: [fileSchema], + acceptedBy: [{ + companyId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Company' + }, + acceptedAt: { + type: Date, + default: Date.now + } + }], + status: { + type: String, + enum: ['draft', 'published'], + default: 'published' + }, + createdAt: { + type: Date, + default: Date.now, + index: true + }, + updatedAt: { + type: Date, + default: Date.now + } +}); + +// Индексы для оптимизации поиска +buyProductSchema.index({ companyId: 1, createdAt: -1 }); +buyProductSchema.index({ name: 'text', description: 'text' }); + +module.exports = mongoose.model('BuyProduct', buyProductSchema); diff --git a/server/routers/procurement/models/Company.js b/server/routers/procurement/models/Company.js new file mode 100644 index 0000000..f9010b3 --- /dev/null +++ b/server/routers/procurement/models/Company.js @@ -0,0 +1,76 @@ +const mongoose = require('mongoose'); + +const companySchema = new mongoose.Schema({ + fullName: { + type: String, + required: true + }, + shortName: String, + inn: { + type: String, + sparse: true + }, + ogrn: String, + legalForm: String, + industry: String, + companySize: String, + website: String, + phone: String, + email: String, + slogan: String, + description: String, + foundedYear: Number, + employeeCount: String, + revenue: String, + legalAddress: String, + actualAddress: String, + bankDetails: String, + logo: String, + rating: { + type: Number, + default: 0, + min: 0, + max: 5 + }, + reviews: { + type: Number, + default: 0 + }, + ownerId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User' + }, + platformGoals: [String], + productsOffered: String, + productsNeeded: String, + partnerIndustries: [String], + partnerGeography: [String], + verified: { + type: Boolean, + default: false + }, + metrics: { + type: { + profileViews: { type: Number, default: 0 } + }, + default: {} + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + collection: 'companies', + minimize: false +}); + +// Индексы для поиска +companySchema.index({ fullName: 'text', shortName: 'text', description: 'text' }); +companySchema.index({ industry: 1 }); +companySchema.index({ rating: -1 }); + +module.exports = mongoose.model('Company', companySchema); diff --git a/server/routers/procurement/models/Experience.js b/server/routers/procurement/models/Experience.js new file mode 100644 index 0000000..09dd018 --- /dev/null +++ b/server/routers/procurement/models/Experience.js @@ -0,0 +1,46 @@ +const mongoose = require('mongoose'); + +const experienceSchema = new mongoose.Schema({ + companyId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Company', + required: true, + index: true + }, + confirmed: { + type: Boolean, + default: false + }, + customer: { + type: String, + required: true + }, + subject: { + type: String, + required: true + }, + volume: { + type: String + }, + contact: { + type: String + }, + comment: { + type: String + }, + createdAt: { + type: Date, + default: Date.now, + index: true + }, + updatedAt: { + type: Date, + default: Date.now + } +}); + +// Индексы для оптимизации поиска +experienceSchema.index({ companyId: 1, createdAt: -1 }); + +module.exports = mongoose.model('Experience', experienceSchema); + diff --git a/server/routers/procurement/models/Message.js b/server/routers/procurement/models/Message.js new file mode 100644 index 0000000..e8afd5a --- /dev/null +++ b/server/routers/procurement/models/Message.js @@ -0,0 +1,37 @@ +const mongoose = require('mongoose'); + +const messageSchema = new mongoose.Schema({ + threadId: { + type: String, + required: true, + index: true + }, + senderCompanyId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Company', + required: true + }, + recipientCompanyId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Company', + required: true + }, + text: { + type: String, + required: true + }, + read: { + type: Boolean, + default: false + }, + timestamp: { + type: Date, + default: Date.now, + index: true + } +}); + +// Индекс для быстрого поиска сообщений потока +messageSchema.index({ threadId: 1, timestamp: -1 }); + +module.exports = mongoose.model('Message', messageSchema); diff --git a/server/routers/procurement/models/Product.js b/server/routers/procurement/models/Product.js new file mode 100644 index 0000000..2f194f7 --- /dev/null +++ b/server/routers/procurement/models/Product.js @@ -0,0 +1,57 @@ +const mongoose = require('mongoose'); + +const productSchema = new mongoose.Schema({ + name: { + type: String, + required: true + }, + category: { + type: String, + required: true + }, + description: { + type: String, + required: true, + minlength: 20, + maxlength: 500 + }, + type: { + type: String, + enum: ['sell', 'buy'], + required: true + }, + productUrl: String, + companyId: { + type: String, + required: true, + index: true + }, + price: String, + unit: String, + minOrder: String, + createdAt: { + type: Date, + default: Date.now, + index: true + }, + updatedAt: { + type: Date, + default: Date.now + } +}); + +// Индекс для поиска +productSchema.index({ companyId: 1, type: 1 }); +productSchema.index({ name: 'text', description: 'text' }); + +// Transform _id to id in JSON output +productSchema.set('toJSON', { + transform: (doc, ret) => { + ret.id = ret._id; + delete ret._id; + delete ret.__v; + return ret; + } +}); + +module.exports = mongoose.model('Product', productSchema); diff --git a/server/routers/procurement/models/Request.js b/server/routers/procurement/models/Request.js new file mode 100644 index 0000000..6cd2412 --- /dev/null +++ b/server/routers/procurement/models/Request.js @@ -0,0 +1,82 @@ +const mongoose = require('mongoose'); + +const requestSchema = new mongoose.Schema({ + senderCompanyId: { + type: String, + required: true, + index: true + }, + recipientCompanyId: { + type: String, + required: true, + index: true + }, + subject: { + type: String, + required: false, + trim: true, + default: '' + }, + text: { + type: String, + required: true + }, + files: [{ + id: { type: String }, + name: { type: String }, + url: { type: String }, + type: { type: String }, + size: { type: Number }, + storagePath: { type: String }, + uploadedAt: { + type: Date, + default: Date.now + } + }], + productId: { + type: String, + ref: 'BuyProduct' + }, + status: { + type: String, + enum: ['pending', 'accepted', 'rejected'], + default: 'pending' + }, + response: { + type: String, + default: null + }, + responseFiles: [{ + id: { type: String }, + name: { type: String }, + url: { type: String }, + type: { type: String }, + size: { type: Number }, + storagePath: { type: String }, + uploadedAt: { + type: Date, + default: Date.now + } + }], + respondedAt: { + type: Date, + default: null + }, + createdAt: { + type: Date, + default: Date.now, + index: true + }, + updatedAt: { + type: Date, + default: Date.now + } +}); + +// Индексы для оптимизации поиска +requestSchema.index({ senderCompanyId: 1, createdAt: -1 }); +requestSchema.index({ recipientCompanyId: 1, createdAt: -1 }); +requestSchema.index({ senderCompanyId: 1, recipientCompanyId: 1 }); +requestSchema.index({ subject: 1, createdAt: -1 }); + +module.exports = mongoose.model('Request', requestSchema); diff --git a/server/routers/procurement/models/Review.js b/server/routers/procurement/models/Review.js new file mode 100644 index 0000000..327c027 --- /dev/null +++ b/server/routers/procurement/models/Review.js @@ -0,0 +1,58 @@ +const mongoose = require('mongoose'); + +const reviewSchema = new mongoose.Schema({ + companyId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Company', + required: true, + index: true + }, + authorCompanyId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Company', + required: true + }, + authorName: { + type: String, + required: true + }, + authorCompany: { + type: String, + required: true + }, + rating: { + type: Number, + required: true, + min: 1, + max: 5 + }, + comment: { + type: String, + required: true, + minlength: 10, + maxlength: 1000 + }, + date: { + type: Date, + default: Date.now + }, + verified: { + type: Boolean, + default: true + }, + createdAt: { + type: Date, + default: Date.now, + index: true + }, + updatedAt: { + type: Date, + default: Date.now + } +}); + +// Индексы для оптимизации поиска +reviewSchema.index({ companyId: 1, createdAt: -1 }); +reviewSchema.index({ authorCompanyId: 1 }); + +module.exports = mongoose.model('Review', reviewSchema); diff --git a/server/routers/procurement/models/User.js b/server/routers/procurement/models/User.js new file mode 100644 index 0000000..0a2ea56 --- /dev/null +++ b/server/routers/procurement/models/User.js @@ -0,0 +1,73 @@ +const mongoose = require('mongoose'); +const bcrypt = require('bcryptjs'); + +const userSchema = new mongoose.Schema({ + email: { + type: String, + required: true, + unique: true, + lowercase: true, + trim: true, + match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + }, + password: { + type: String, + required: true, + minlength: 8 + }, + firstName: { + type: String, + required: true + }, + lastName: { + type: String, + required: true + }, + position: String, + phone: String, + companyId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Company' + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + collection: 'users', + minimize: false, + toObject: { versionKey: false } +}); + +userSchema.set('toObject', { virtuals: false, versionKey: false }); + +// Хешировать пароль перед сохранением +userSchema.pre('save', async function(next) { + if (!this.isModified('password')) return next(); + + try { + const salt = await bcrypt.genSalt(10); + this.password = await bcrypt.hash(this.password, salt); + next(); + } catch (error) { + next(error); + } +}); + +// Метод для сравнения паролей +userSchema.methods.comparePassword = async function(candidatePassword) { + return await bcrypt.compare(candidatePassword, this.password); +}; + +// Скрыть пароль при преобразовании в JSON +userSchema.methods.toJSON = function() { + const obj = this.toObject(); + delete obj.password; + return obj; +}; + +module.exports = mongoose.model('User', userSchema); diff --git a/server/routers/procurement/routes/__tests__/buyProducts.test.js b/server/routers/procurement/routes/__tests__/buyProducts.test.js new file mode 100644 index 0000000..0188984 --- /dev/null +++ b/server/routers/procurement/routes/__tests__/buyProducts.test.js @@ -0,0 +1,240 @@ +const express = require('express') +const mongoose = require('mongoose') +const request = require('supertest') +const { describe, it, beforeAll, expect } = require('@jest/globals') + +// Mock auth middleware +const mockAuthMiddleware = (req, res, next) => { + req.user = { + companyId: 'test-company-id', + id: 'test-user-id', + } + next() +} + +describe('Buy Products Routes', () => { + let app + let router + + beforeAll(() => { + app = express() + app.use(express.json()) + + // Create a test router with mock middleware + router = express.Router() + + // Mock endpoints for testing structure + router.get('/company/:companyId', mockAuthMiddleware, (req, res) => { + res.json([]) + }) + + router.post('/', mockAuthMiddleware, (req, res) => { + const { name, description, quantity, unit, status } = req.body + + if (!name || !description || !quantity) { + return res.status(400).json({ + error: 'name, description, and quantity are required', + }) + } + + if (description.trim().length < 10) { + return res.status(400).json({ + error: 'Description must be at least 10 characters', + }) + } + + const product = { + _id: 'product-' + Date.now(), + companyId: req.user.companyId, + name: name.trim(), + description: description.trim(), + quantity: quantity.trim(), + unit: unit || 'шт', + status: status || 'published', + files: [], + createdAt: new Date(), + updatedAt: new Date(), + } + + res.status(201).json(product) + }) + + app.use('/buy-products', router) + }) + + describe('GET /buy-products/company/:companyId', () => { + it('should return products list for a company', async () => { + const res = await request(app) + .get('/buy-products/company/test-company-id') + .expect(200) + + expect(Array.isArray(res.body)).toBe(true) + }) + + it('should require authentication', async () => { + // This test would fail without proper auth middleware + const res = await request(app) + .get('/buy-products/company/test-company-id') + + expect(res.status).toBeLessThan(500) + }) + }) + + describe('POST /buy-products', () => { + it('should create a new product with valid data', async () => { + const productData = { + name: 'Test Product', + description: 'This is a test product description', + quantity: '10', + unit: 'шт', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(201) + + expect(res.body).toHaveProperty('_id') + expect(res.body.name).toBe('Test Product') + expect(res.body.description).toBe(productData.description) + expect(res.body.status).toBe('published') + }) + + it('should reject product without name', async () => { + const productData = { + description: 'This is a test product description', + quantity: '10', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(400) + + expect(res.body.error).toContain('required') + }) + + it('should reject product without description', async () => { + const productData = { + name: 'Test Product', + quantity: '10', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(400) + + expect(res.body.error).toContain('required') + }) + + it('should reject product without quantity', async () => { + const productData = { + name: 'Test Product', + description: 'This is a test product description', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(400) + + expect(res.body.error).toContain('required') + }) + + it('should reject product with description less than 10 characters', async () => { + const productData = { + name: 'Test Product', + description: 'short', + quantity: '10', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(400) + + expect(res.body.error).toContain('10 characters') + }) + + it('should set default unit to "шт" if not provided', async () => { + const productData = { + name: 'Test Product', + description: 'This is a test product description', + quantity: '10', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(201) + + expect(res.body.unit).toBe('шт') + }) + + it('should use provided unit', async () => { + const productData = { + name: 'Test Product', + description: 'This is a test product description', + quantity: '10', + unit: 'кг', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(201) + + expect(res.body.unit).toBe('кг') + }) + + it('should set status to "published" by default', async () => { + const productData = { + name: 'Test Product', + description: 'This is a test product description', + quantity: '10', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(201) + + expect(res.body.status).toBe('published') + }) + }) + + describe('Data validation', () => { + it('should trim whitespace from product data', async () => { + const productData = { + name: ' Test Product ', + description: ' This is a test product description ', + quantity: ' 10 ', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(201) + + expect(res.body.name).toBe('Test Product') + expect(res.body.description).toBe('This is a test product description') + expect(res.body.quantity).toBe('10') + }) + + it('should include companyId from auth token', async () => { + const productData = { + name: 'Test Product', + description: 'This is a test product description', + quantity: '10', + } + + const res = await request(app) + .post('/buy-products') + .send(productData) + .expect(201) + + expect(res.body.companyId).toBe('test-company-id') + }) + }) +}) diff --git a/server/routers/procurement/routes/activity.js b/server/routers/procurement/routes/activity.js new file mode 100644 index 0000000..207ecc1 --- /dev/null +++ b/server/routers/procurement/routes/activity.js @@ -0,0 +1,101 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Activity = require('../models/Activity'); +const User = require('../models/User'); + +// Получить последние активности компании +router.get('/', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.json({ activities: [] }); + } + + const companyId = user.companyId.toString(); + const limit = parseInt(req.query.limit) || 10; + + const activities = await Activity.find({ companyId }) + .sort({ createdAt: -1 }) + .limit(limit) + .lean(); + + res.json({ activities }); + } catch (error) { + console.error('Error getting activities:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Отметить активность как прочитанную +router.patch('/:id/read', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.status(403).json({ error: 'Access denied' }); + } + + const companyId = user.companyId.toString(); + const activityId = req.params.id; + + const activity = await Activity.findOne({ + _id: activityId, + companyId + }); + + if (!activity) { + return res.status(404).json({ error: 'Activity not found' }); + } + + activity.read = true; + await activity.save(); + + res.json({ success: true, activity }); + } catch (error) { + console.error('Error updating activity:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Отметить все активности как прочитанные +router.post('/mark-all-read', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.status(403).json({ error: 'Access denied' }); + } + + const companyId = user.companyId.toString(); + + await Activity.updateMany( + { companyId, read: false }, + { $set: { read: true } } + ); + + res.json({ success: true }); + } catch (error) { + console.error('Error marking all as read:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Создать активность (вспомогательная функция) +router.createActivity = async (data) => { + try { + const activity = new Activity(data); + await activity.save(); + return activity; + } catch (error) { + console.error('Error creating activity:', error); + throw error; + } +}; + +module.exports = router; + diff --git a/server/routers/procurement/routes/auth.js b/server/routers/procurement/routes/auth.js new file mode 100644 index 0000000..254a152 --- /dev/null +++ b/server/routers/procurement/routes/auth.js @@ -0,0 +1,517 @@ +const express = require('express'); +const router = express.Router(); +const { generateToken, verifyToken } = require('../middleware/auth'); +const User = require('../models/User'); +const Company = require('../models/Company'); +const Request = require('../models/Request'); +const BuyProduct = require('../models/BuyProduct'); +const Message = require('../models/Message'); +const Review = require('../models/Review'); +const mongoose = require('../../../utils/mongoose'); +const { Types } = mongoose; + +const PRESET_COMPANY_ID = new Types.ObjectId('68fe2ccda3526c303ca06796'); +const PRESET_USER_EMAIL = 'admin@test-company.ru'; + +const changePasswordFlow = async (userId, currentPassword, newPassword) => { + if (!currentPassword || !newPassword) { + return { status: 400, body: { error: 'Current password and new password are required' } }; + } + + if (typeof newPassword !== 'string' || newPassword.trim().length < 8) { + return { status: 400, body: { error: 'New password must be at least 8 characters long' } }; + } + + const user = await User.findById(userId); + + if (!user) { + return { status: 404, body: { error: 'User not found' } }; + } + + const isMatch = await user.comparePassword(currentPassword); + + if (!isMatch) { + return { status: 400, body: { error: 'Current password is incorrect' } }; + } + + user.password = newPassword; + user.updatedAt = new Date(); + await user.save(); + + return { status: 200, body: { message: 'Password updated successfully' } }; +}; + +const deleteAccountFlow = async (userId, password) => { + if (!password) { + return { status: 400, body: { error: 'Password is required to delete account' } }; + } + + const user = await User.findById(userId); + + if (!user) { + return { status: 404, body: { error: 'User not found' } }; + } + + const validPassword = await user.comparePassword(password); + + if (!validPassword) { + return { status: 400, body: { error: 'Password is incorrect' } }; + } + + const companyId = user.companyId ? user.companyId.toString() : null; + const companyObjectId = companyId && Types.ObjectId.isValid(companyId) ? new Types.ObjectId(companyId) : null; + + const cleanupTasks = []; + + if (companyId) { + cleanupTasks.push(Request.deleteMany({ + $or: [{ senderCompanyId: companyId }, { recipientCompanyId: companyId }], + })); + + cleanupTasks.push(BuyProduct.deleteMany({ companyId })); + + if (companyObjectId) { + cleanupTasks.push(Message.deleteMany({ + $or: [ + { senderCompanyId: companyObjectId }, + { recipientCompanyId: companyObjectId }, + ], + })); + + cleanupTasks.push(Review.deleteMany({ + $or: [ + { companyId: companyObjectId }, + { authorCompanyId: companyObjectId }, + ], + })); + } + + cleanupTasks.push(Company.findByIdAndDelete(companyId)); + } + + cleanupTasks.push(User.findByIdAndDelete(user._id)); + + await Promise.all(cleanupTasks); + + return { status: 200, body: { message: 'Account deleted successfully' } }; +}; + +// Функция для логирования с проверкой DEV переменной +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +const waitForDatabaseConnection = async () => { + const isAuthFailure = (error) => { + if (!error) return false; + if (error.code === 13 || error.code === 18) return true; + return /auth/i.test(String(error.message || '')); + }; + + const verifyAuth = async () => { + try { + await mongoose.connection.db.admin().command({ listDatabases: 1 }); + return true; + } catch (error) { + if (isAuthFailure(error)) { + return false; + } + throw error; + } + }; + + for (let attempt = 0; attempt < 3; attempt++) { + if (mongoose.connection.readyState === 1) { + const authed = await verifyAuth(); + if (authed) { + return; + } + await mongoose.connection.close().catch(() => {}); + } + + try { + // eslint-disable-next-line no-undef + const connection = await connectDB(); + if (!connection) { + break; + } + + const authed = await verifyAuth(); + if (authed) { + return; + } + + await mongoose.connection.close().catch(() => {}); + } catch (error) { + if (!isAuthFailure(error)) { + throw error; + } + } + } + + throw new Error('Unable to authenticate with MongoDB'); +}; + +// Инициализация тестового пользователя +const initializeTestUser = async () => { + try { + await waitForDatabaseConnection(); + + let company = await Company.findById(PRESET_COMPANY_ID); + if (!company) { + company = await Company.create({ + _id: PRESET_COMPANY_ID, + fullName: 'ООО "Тестовая Компания"', + shortName: 'ООО "Тест"', + inn: '7707083893', + ogrn: '1027700132195', + legalForm: 'ООО', + industry: 'Производство', + companySize: '50-100', + partnerGeography: ['moscow', 'russia_all'], + website: 'https://test-company.ru', + verified: true, + rating: 4.5, + description: 'Ведущая компания в области производства', + slogan: 'Качество и инновация' + }); + log('✅ Test company initialized'); + } else { + await Company.updateOne( + { _id: PRESET_COMPANY_ID }, + { + $set: { + fullName: 'ООО "Тестовая Компания"', + shortName: 'ООО "Тест"', + industry: 'Производство', + companySize: '50-100', + partnerGeography: ['moscow', 'russia_all'], + website: 'https://test-company.ru', + }, + } + ); + } + + let existingUser = await User.findOne({ email: PRESET_USER_EMAIL }); + if (!existingUser) { + existingUser = await User.create({ + email: PRESET_USER_EMAIL, + password: 'SecurePass123!', + firstName: 'Иван', + lastName: 'Петров', + position: 'Генеральный директор', + companyId: PRESET_COMPANY_ID + }); + log('✅ Test user initialized'); + } else if (!existingUser.companyId || existingUser.companyId.toString() !== PRESET_COMPANY_ID.toString()) { + existingUser.companyId = PRESET_COMPANY_ID; + existingUser.updatedAt = new Date(); + await existingUser.save(); + log('ℹ️ Test user company reference was fixed'); + } + } catch (error) { + console.error('Error initializing test data:', error.message); + if (error?.code === 13 || /auth/i.test(error?.message || '')) { + try { + // eslint-disable-next-line no-undef + await connectDB(); + } catch (connectError) { + if (process.env.DEV === 'true') { + console.error('Failed to re-connect after auth error:', connectError.message); + } + } + } + } +}; + +initializeTestUser(); + +// Регистрация +router.post('/register', async (req, res) => { + try { + await waitForDatabaseConnection(); + + const { email, password, firstName, lastName, position, phone, fullName, inn, ogrn, legalForm, industry, companySize, website } = req.body; + + // Проверка обязательных полей + if (!email || !password || !firstName || !lastName || !fullName) { + return res.status(400).json({ error: 'Missing required fields' }); + } + + // Проверка существования пользователя + const existingUser = await User.findOne({ email }); + if (existingUser) { + return res.status(409).json({ error: 'User already exists' }); + } + + // Создать компанию + let company; + try { + company = new Company({ + fullName, + shortName: fullName.substring(0, 20), + inn, + ogrn, + legalForm, + industry, + companySize, + website, + verified: false, + rating: 0, + description: '', + slogan: '', + partnerGeography: ['moscow', 'russia_all'] + }); + const savedCompany = await company.save(); + company = savedCompany; + log('✅ Company saved:', company._id, 'Result:', savedCompany ? 'Success' : 'Failed'); + } catch (err) { + console.error('Company save error:', err); + return res.status(400).json({ error: 'Failed to create company: ' + err.message }); + } + + // Создать пользователя + try { + const newUser = await User.create({ + email, + password, + firstName, + lastName, + position: position || '', + phone: phone || '', + companyId: company._id + }); + + log('✅ User created:', newUser._id); + + const token = generateToken(newUser._id.toString(), newUser.companyId.toString(), newUser.firstName, newUser.lastName, company.fullName); + return res.status(201).json({ + tokens: { + accessToken: token, + refreshToken: token + }, + user: { + id: newUser._id.toString(), + email: newUser.email, + firstName: newUser.firstName, + lastName: newUser.lastName, + position: newUser.position, + companyId: newUser.companyId.toString() + }, + company: { + id: company._id.toString(), + name: company.fullName, + inn: company.inn + } + }); + } catch (err) { + console.error('User creation error:', err); + return res.status(400).json({ error: 'Failed to create user: ' + err.message }); + } + } catch (error) { + console.error('Registration error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Вход +router.post('/login', async (req, res) => { + try { + if (process.env.DEV === 'true') { + console.log('[Auth] /login called'); + } + await waitForDatabaseConnection(); + if (process.env.DEV === 'true') { + console.log('[Auth] DB ready, running login query'); + } + + const { email, password } = req.body; + + if (!email || !password) { + return res.status(400).json({ error: 'Email and password required' }); + } + + const user = await User.findOne({ email }); + if (!user) { + return res.status(401).json({ error: 'Invalid credentials' }); + } + + const isMatch = await user.comparePassword(password); + if (!isMatch) { + return res.status(401).json({ error: 'Invalid credentials' }); + } + + if ( + user.email === PRESET_USER_EMAIL && + (!user.companyId || user.companyId.toString() !== PRESET_COMPANY_ID.toString()) + ) { + await User.updateOne( + { _id: user._id }, + { $set: { companyId: PRESET_COMPANY_ID, updatedAt: new Date() } } + ); + user.companyId = PRESET_COMPANY_ID; + } + + // Получить компанию до использования в generateToken + let companyData = null; + try { + companyData = user.companyId ? await Company.findById(user.companyId) : null; + } catch (err) { + console.error('Failed to fetch company:', err.message); + } + + if (user.email === PRESET_USER_EMAIL) { + try { + companyData = await Company.findByIdAndUpdate( + PRESET_COMPANY_ID, + { + $set: { + fullName: 'ООО "Тестовая Компания"', + shortName: 'ООО "Тест"', + inn: '7707083893', + ogrn: '1027700132195', + legalForm: 'ООО', + industry: 'Производство', + companySize: '50-100', + partnerGeography: ['moscow', 'russia_all'], + website: 'https://test-company.ru', + verified: true, + rating: 4.5, + description: 'Ведущая компания в области производства', + slogan: 'Качество и инновация', + updatedAt: new Date(), + }, + }, + { upsert: true, new: true, setDefaultsOnInsert: true } + ); + } catch (err) { + console.error('Failed to ensure preset company:', err.message); + } + } + + const token = generateToken(user._id.toString(), user.companyId.toString(), user.firstName, user.lastName, companyData?.fullName || 'Company'); + log('✅ Token generated for user:', user._id); + + res.json({ + tokens: { + accessToken: token, + refreshToken: token + }, + user: { + id: user._id.toString(), + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + position: user.position, + companyId: user.companyId.toString() + }, + company: companyData ? { + id: companyData._id.toString(), + name: companyData.fullName, + inn: companyData.inn + } : null + }); + } catch (error) { + console.error('Login error:', error); + res.status(500).json({ error: `LOGIN_ERROR: ${error.message}` }); + } +}); + +// Смена пароля +router.post('/change-password', verifyToken, async (req, res) => { + try { + const { currentPassword, newPassword } = req.body || {}; + const result = await changePasswordFlow(req.userId, currentPassword, newPassword); + res.status(result.status).json(result.body); + } catch (error) { + console.error('Change password error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Удаление аккаунта +router.delete('/account', verifyToken, async (req, res) => { + try { + const { password } = req.body || {}; + const result = await deleteAccountFlow(req.userId, password); + res.status(result.status).json(result.body); + } catch (error) { + console.error('Delete account error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Обновить профиль / универсальные действия +router.patch('/profile', verifyToken, async (req, res) => { + try { + const rawAction = req.body?.action || req.query?.action || req.body?.type; + const payload = req.body?.payload || req.body || {}; + const action = typeof rawAction === 'string' ? rawAction : ''; + + if (action === 'changePassword') { + const result = await changePasswordFlow(req.userId, payload.currentPassword, payload.newPassword); + return res.status(result.status).json(result.body); + } + + if (action === 'deleteAccount') { + const result = await deleteAccountFlow(req.userId, payload.password); + return res.status(result.status).json(result.body); + } + + if (action === 'updateProfile') { + await waitForDatabaseConnection(); + + const { firstName, lastName, position, phone } = payload; + + if (!firstName && !lastName && !position && !phone) { + return res.status(400).json({ error: 'At least one field must be provided' }); + } + + const user = await User.findById(req.userId); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + if (firstName) user.firstName = firstName; + if (lastName) user.lastName = lastName; + if (position !== undefined) user.position = position; + if (phone !== undefined) user.phone = phone; + user.updatedAt = new Date(); + + await user.save(); + + const company = user.companyId ? await Company.findById(user.companyId) : null; + + return res.json({ + message: 'Profile updated successfully', + user: { + id: user._id.toString(), + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + position: user.position, + phone: user.phone, + companyId: user.companyId?.toString() + }, + company: company ? { + id: company._id.toString(), + name: company.fullName, + inn: company.inn + } : null + }); + } + + res.json({ message: 'Profile endpoint' }); + } catch (error) { + console.error('Profile update error:', error); + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/server/routers/procurement/routes/buy.js b/server/routers/procurement/routes/buy.js new file mode 100644 index 0000000..58b1d66 --- /dev/null +++ b/server/routers/procurement/routes/buy.js @@ -0,0 +1,221 @@ +const express = require('express') +const fs = require('fs') +const path = require('path') +const router = express.Router() +const BuyDocument = require('../models/BuyDocument') + +// Create remote-assets/docs directory if it doesn't exist +const docsDir = 'server/routers/remote-assets/docs' +if (!fs.existsSync(docsDir)) { + fs.mkdirSync(docsDir, { recursive: true }) +} + +function generateId() { + return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}` +} + +// GET /buy/docs?ownerCompanyId=... +router.get('/docs', async (req, res) => { + try { + const { ownerCompanyId } = req.query + console.log('[BUY API] GET /docs', { ownerCompanyId }) + + let query = {} + if (ownerCompanyId) { + query.ownerCompanyId = ownerCompanyId + } + + const docs = await BuyDocument.find(query).sort({ createdAt: -1 }) + + const result = docs.map(doc => ({ + ...doc.toObject(), + url: `/api/buy/docs/${doc.id}/file` + })) + + res.json(result) + } catch (error) { + console.error('[BUY API] Error fetching docs:', error) + res.status(500).json({ error: 'Failed to fetch documents' }) + } +}) + +// POST /buy/docs +router.post('/docs', async (req, res) => { + try { + const { ownerCompanyId, name, type, fileData } = req.body || {} + console.log('[BUY API] POST /docs', { ownerCompanyId, name, type }) + + if (!ownerCompanyId || !name || !type) { + return res.status(400).json({ error: 'ownerCompanyId, name and type are required' }) + } + + if (!fileData) { + return res.status(400).json({ error: 'fileData is required' }) + } + + const id = generateId() + + // Save file to disk + const binaryData = Buffer.from(fileData, 'base64') + const filePath = `${docsDir}/${id}.${type}` + fs.writeFileSync(filePath, binaryData) + console.log(`[BUY API] File saved to ${filePath}, size: ${binaryData.length} bytes`) + + const size = binaryData.length + + const doc = await BuyDocument.create({ + id, + ownerCompanyId, + name, + type, + size, + filePath, + acceptedBy: [] + }) + + console.log('[BUY API] Document created:', id) + + res.status(201).json({ + ...doc.toObject(), + url: `/api/buy/docs/${doc.id}/file` + }) + } catch (e) { + console.error(`[BUY API] Error saving file: ${e.message}`) + res.status(500).json({ error: 'Failed to save file' }) + } +}) + +router.post('/docs/:id/accept', async (req, res) => { + try { + const { id } = req.params + const { companyId } = req.body || {} + console.log('[BUY API] POST /docs/:id/accept', { id, companyId }) + + if (!companyId) { + return res.status(400).json({ error: 'companyId is required' }) + } + + const doc = await BuyDocument.findOne({ id }) + if (!doc) { + console.log('[BUY API] Document not found:', id) + return res.status(404).json({ error: 'Document not found' }) + } + + if (!doc.acceptedBy.includes(companyId)) { + doc.acceptedBy.push(companyId) + await doc.save() + } + + res.json({ id: doc.id, acceptedBy: doc.acceptedBy }) + } catch (error) { + console.error('[BUY API] Error accepting document:', error) + res.status(500).json({ error: 'Failed to accept document' }) + } +}) + +router.get('/docs/:id/delete', async (req, res) => { + try { + const { id } = req.params + console.log('[BUY API] GET /docs/:id/delete', { id }) + + const doc = await BuyDocument.findOne({ id }) + if (!doc) { + console.log('[BUY API] Document not found for deletion:', id) + return res.status(404).json({ error: 'Document not found' }) + } + + // Delete file from disk + if (doc.filePath && fs.existsSync(doc.filePath)) { + try { + fs.unlinkSync(doc.filePath) + console.log(`[BUY API] File deleted: ${doc.filePath}`) + } catch (e) { + console.error(`[BUY API] Error deleting file: ${e.message}`) + } + } + + await BuyDocument.deleteOne({ id }) + + console.log('[BUY API] Document deleted via GET:', id) + res.json({ id: doc.id, success: true }) + } catch (error) { + console.error('[BUY API] Error deleting document:', error) + res.status(500).json({ error: 'Failed to delete document' }) + } +}) + +router.delete('/docs/:id', async (req, res) => { + try { + const { id } = req.params + console.log('[BUY API] DELETE /docs/:id', { id }) + + const doc = await BuyDocument.findOne({ id }) + if (!doc) { + console.log('[BUY API] Document not found for deletion:', id) + return res.status(404).json({ error: 'Document not found' }) + } + + // Delete file from disk + if (doc.filePath && fs.existsSync(doc.filePath)) { + try { + fs.unlinkSync(doc.filePath) + console.log(`[BUY API] File deleted: ${doc.filePath}`) + } catch (e) { + console.error(`[BUY API] Error deleting file: ${e.message}`) + } + } + + await BuyDocument.deleteOne({ id }) + + console.log('[BUY API] Document deleted:', id) + res.json({ id: doc.id, success: true }) + } catch (error) { + console.error('[BUY API] Error deleting document:', error) + res.status(500).json({ error: 'Failed to delete document' }) + } +}) + +// GET /buy/docs/:id/file - Serve the file +router.get('/docs/:id/file', async (req, res) => { + try { + const { id } = req.params + console.log('[BUY API] GET /docs/:id/file', { id }) + + const doc = await BuyDocument.findOne({ id }) + if (!doc) { + console.log('[BUY API] Document not found:', id) + return res.status(404).json({ error: 'Document not found' }) + } + + const filePath = `${docsDir}/${id}.${doc.type}` + if (!fs.existsSync(filePath)) { + console.log('[BUY API] File not found on disk:', filePath) + return res.status(404).json({ error: 'File not found on disk' }) + } + + const fileBuffer = fs.readFileSync(filePath) + + const mimeTypes = { + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'pdf': 'application/pdf' + } + + const mimeType = mimeTypes[doc.type] || 'application/octet-stream' + // eslint-disable-next-line no-useless-escape + const sanitizedName = doc.name.replace(/[^\w\s\-\.]/g, '_') + + res.setHeader('Content-Type', mimeType) + const encodedFilename = encodeURIComponent(`${doc.name}.${doc.type}`) + res.setHeader('Content-Disposition', `attachment; filename="${sanitizedName}.${doc.type}"; filename*=UTF-8''${encodedFilename}`) + res.setHeader('Content-Length', fileBuffer.length) + + console.log(`[BUY API] Serving file ${id} from ${filePath} (${fileBuffer.length} bytes)`) + res.send(fileBuffer) + } catch (e) { + console.error(`[BUY API] Error serving file: ${e.message}`) + res.status(500).json({ error: 'Error serving file' }) + } +}) + +module.exports = router \ No newline at end of file diff --git a/server/routers/procurement/routes/buyProducts.js b/server/routers/procurement/routes/buyProducts.js new file mode 100644 index 0000000..af0912c --- /dev/null +++ b/server/routers/procurement/routes/buyProducts.js @@ -0,0 +1,503 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const BuyProduct = require('../models/BuyProduct'); +const path = require('path'); +const fs = require('fs'); +const multer = require('multer'); +const UPLOADS_ROOT = 'server/routers/remote-assets/uploads/buy-products'; +const ensureDirectory = (dirPath) => { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +}; + +ensureDirectory(UPLOADS_ROOT); + +const MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB +const ALLOWED_MIME_TYPES = new Set([ + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'text/csv', +]); + +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + const productId = req.params.id || 'common'; + const productDir = `${UPLOADS_ROOT}/${productId}`; + ensureDirectory(productDir); + cb(null, productDir); + }, + filename: (req, file, cb) => { + // Исправляем кодировку имени файла из Latin1 в UTF-8 + const fixedName = Buffer.from(file.originalname, 'latin1').toString('utf8'); + const originalExtension = path.extname(fixedName) || ''; + const baseName = path + .basename(fixedName, originalExtension) + // eslint-disable-next-line no-control-regex + .replace(/[<>:"/\\|?*\x00-\x1F]+/g, '_'); // Убираем только недопустимые символы Windows, оставляем кириллицу + cb(null, `${Date.now()}_${baseName}${originalExtension}`); + }, +}); + +const upload = multer({ + storage, + limits: { + fileSize: MAX_FILE_SIZE, + }, + fileFilter: (req, file, cb) => { + if (ALLOWED_MIME_TYPES.has(file.mimetype)) { + cb(null, true); + return; + } + + req.fileValidationError = 'UNSUPPORTED_FILE_TYPE'; + cb(null, false); + }, +}); + +const handleSingleFileUpload = (req, res, next) => { + upload.single('file')(req, res, (err) => { + if (err) { + console.error('[BuyProducts] Multer error:', err.message); + if (err.code === 'LIMIT_FILE_SIZE') { + return res.status(400).json({ error: 'File is too large. Maximum size is 15MB.' }); + } + return res.status(400).json({ error: err.message }); + } + next(); + }); +}; + + +// Функция для логирования с проверкой DEV переменной +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +// GET /buy-products/company/:companyId - получить товары компании +router.get('/company/:companyId', verifyToken, async (req, res) => { + try { + const { companyId } = req.params; + + log('[BuyProducts] Fetching products for company:', companyId); + const products = await BuyProduct.find({ companyId }) + .sort({ createdAt: -1 }) + .exec(); + + log('[BuyProducts] Found', products.length, 'products for company', companyId); + log('[BuyProducts] Products:', products); + + res.json(products); + } catch (error) { + console.error('[BuyProducts] Error fetching products:', error.message); + console.error('[BuyProducts] Error stack:', error.stack); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + }); + } +}); + +// POST /buy-products - создать новый товар +router.post('/', verifyToken, async (req, res) => { + try { + const { name, description, quantity, unit, status } = req.body; + + log('[BuyProducts] Creating new product:', { name, description, quantity, companyId: req.companyId }); + + if (!name || !description || !quantity) { + return res.status(400).json({ + error: 'name, description, and quantity are required', + }); + } + + if (description.trim().length < 10) { + return res.status(400).json({ + error: 'Description must be at least 10 characters', + }); + } + + const newProduct = new BuyProduct({ + companyId: req.companyId, + name: name.trim(), + description: description.trim(), + quantity: quantity.trim(), + unit: unit || 'шт', + status: status || 'published', + files: [], + }); + + log('[BuyProducts] Attempting to save product to DB...'); + const savedProduct = await newProduct.save(); + + log('[BuyProducts] New product created successfully:', savedProduct._id); + log('[BuyProducts] Product data:', savedProduct); + + res.status(201).json(savedProduct); + } catch (error) { + console.error('[BuyProducts] Error creating product:', error.message); + console.error('[BuyProducts] Error stack:', error.stack); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + }); + } +}); + +// PUT /buy-products/:id - обновить товар +router.put('/:id', verifyToken, async (req, res) => { + try { + const { id } = req.params; + const { name, description, quantity, unit, status } = req.body; + + const product = await BuyProduct.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + // Проверить, что товар принадлежит текущей компании + if (product.companyId !== req.companyId) { + return res.status(403).json({ error: 'Not authorized' }); + } + + // Обновить поля + if (name) product.name = name.trim(); + if (description) product.description = description.trim(); + if (quantity) product.quantity = quantity.trim(); + if (unit) product.unit = unit; + if (status) product.status = status; + product.updatedAt = new Date(); + + const updatedProduct = await product.save(); + + log('[BuyProducts] Product updated:', id); + + res.json(updatedProduct); + } catch (error) { + console.error('[BuyProducts] Error:', error.message); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + }); + } +}); + +// DELETE /buy-products/:id - удалить товар +router.delete('/:id', verifyToken, async (req, res) => { + try { + const { id } = req.params; + + const product = await BuyProduct.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + if (product.companyId.toString() !== req.companyId.toString()) { + return res.status(403).json({ error: 'Not authorized' }); + } + + await BuyProduct.findByIdAndDelete(id); + + log('[BuyProducts] Product deleted:', id); + + res.json({ message: 'Product deleted successfully' }); + } catch (error) { + console.error('[BuyProducts] Error:', error.message); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + }); + } +}); + +// POST /buy-products/:id/files - добавить файл к товару +router.post('/:id/files', verifyToken, handleSingleFileUpload, async (req, res) => { + try { + const { id } = req.params; + const product = await BuyProduct.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + // Только владелец товара может добавить файл + const productCompanyId = product.companyId?.toString() || product.companyId; + const requestCompanyId = req.companyId?.toString() || req.companyId; + + console.log('[BuyProducts] Comparing company IDs:', { + productCompanyId, + requestCompanyId, + match: productCompanyId === requestCompanyId + }); + + if (productCompanyId !== requestCompanyId) { + return res.status(403).json({ error: 'Not authorized' }); + } + + if (req.fileValidationError) { + return res.status(400).json({ error: 'Unsupported file type. Use PDF, DOC, DOCX, XLS, XLSX or CSV.' }); + } + + if (!req.file) { + return res.status(400).json({ error: 'File is required' }); + } + + // Исправляем кодировку имени файла из Latin1 в UTF-8 + const fixedFileName = Buffer.from(req.file.originalname, 'latin1').toString('utf8'); + + // Извлекаем timestamp из имени файла, созданного multer (формат: {timestamp}_{name}.ext) + const fileTimestamp = req.file.filename.split('_')[0]; + + // storagePath относительно UPLOADS_ROOT (который уже включает 'buy-products') + const relativePath = `${id}/${req.file.filename}`; + const file = { + id: `file-${fileTimestamp}`, // Используем тот же timestamp, что и в имени файла + name: fixedFileName, + url: `/uploads/buy-products/${relativePath}`, + type: req.file.mimetype, + size: req.file.size, + uploadedAt: new Date(), + storagePath: relativePath, + }; + + console.log('[BuyProducts] Adding file to product:', { + productId: id, + fileName: file.name, + fileSize: file.size, + filePath: relativePath + }); + + console.log('[BuyProducts] File object:', JSON.stringify(file, null, 2)); + + // Используем findByIdAndUpdate вместо save() для избежания проблем с валидацией + let updatedProduct; + try { + console.log('[BuyProducts] Calling findByIdAndUpdate with id:', id); + updatedProduct = await BuyProduct.findByIdAndUpdate( + id, + { + $push: { files: file }, + $set: { updatedAt: new Date() } + }, + { new: true, runValidators: false } + ); + console.log('[BuyProducts] findByIdAndUpdate completed'); + } catch (updateError) { + console.error('[BuyProducts] findByIdAndUpdate error:', { + message: updateError.message, + name: updateError.name, + code: updateError.code + }); + throw updateError; + } + + if (!updatedProduct) { + throw new Error('Failed to update product with file'); + } + + console.log('[BuyProducts] File added successfully to product:', id); + + log('[BuyProducts] File added to product:', id, file.name); + + res.json(updatedProduct); + } catch (error) { + console.error('[BuyProducts] Error adding file:', error.message); + console.error('[BuyProducts] Error stack:', error.stack); + console.error('[BuyProducts] Error name:', error.name); + if (error.errors) { + console.error('[BuyProducts] Validation errors:', JSON.stringify(error.errors, null, 2)); + } + res.status(500).json({ + error: 'Internal server error', + message: error.message, + details: error.errors || {}, + }); + } +}); + +// DELETE /buy-products/:id/files/:fileId - удалить файл +router.delete('/:id/files/:fileId', verifyToken, async (req, res) => { + try { + const { id, fileId } = req.params; + + const product = await BuyProduct.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + if (product.companyId.toString() !== req.companyId.toString()) { + return res.status(403).json({ error: 'Not authorized' }); + } + + const fileToRemove = product.files.find((f) => f.id === fileId); + if (!fileToRemove) { + return res.status(404).json({ error: 'File not found' }); + } + + product.files = product.files.filter(f => f.id !== fileId); + await product.save(); + + const storedPath = fileToRemove.storagePath || fileToRemove.url.replace(/^\/uploads\//, ''); + const absolutePath = `server/routers/remote-assets/uploads/${storedPath}`; + + fs.promises.unlink(absolutePath).catch((unlinkError) => { + if (unlinkError && unlinkError.code !== 'ENOENT') { + console.error('[BuyProducts] Failed to remove file from disk:', unlinkError.message); + } + }); + + log('[BuyProducts] File deleted from product:', id, fileId); + + res.json(product); + } catch (error) { + console.error('[BuyProducts] Error deleting file:', error.message); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + }); + } +}); + +// POST /buy-products/:id/accept - акцептировать товар +router.post('/:id/accept', verifyToken, async (req, res) => { + try { + const { id } = req.params; + const companyId = req.companyId; + + const product = await BuyProduct.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + // Не можем акцептировать собственный товар + if (product.companyId.toString() === companyId.toString()) { + return res.status(403).json({ error: 'Cannot accept own product' }); + } + + // Проверить, не акцептировал ли уже + const alreadyAccepted = product.acceptedBy.some( + a => a.companyId.toString() === companyId.toString() + ); + + if (alreadyAccepted) { + return res.status(400).json({ error: 'Already accepted' }); + } + + product.acceptedBy.push({ + companyId, + acceptedAt: new Date() + }); + + await product.save(); + + log('[BuyProducts] Product accepted by company:', companyId); + + res.json(product); + } catch (error) { + console.error('[BuyProducts] Error accepting product:', error.message); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + }); + } +}); + +// GET /buy-products/:id/acceptances - получить компании которые акцептовали +router.get('/:id/acceptances', verifyToken, async (req, res) => { + try { + const { id } = req.params; + + const product = await BuyProduct.findById(id).populate('acceptedBy.companyId', 'shortName fullName'); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + log('[BuyProducts] Returned acceptances for product:', id); + + res.json(product.acceptedBy); + } catch (error) { + console.error('[BuyProducts] Error fetching acceptances:', error.message); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + }); + } +}); + +// GET /buy-products/download/:id/:fileId - скачать файл +router.get('/download/:id/:fileId', verifyToken, async (req, res) => { + try { + console.log('[BuyProducts] Download request received:', { + productId: req.params.id, + fileId: req.params.fileId, + userId: req.userId, + companyId: req.companyId, + headers: req.headers.authorization + }); + + const { id, fileId } = req.params; + const product = await BuyProduct.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + const file = product.files.find((f) => f.id === fileId); + if (!file) { + return res.status(404).json({ error: 'File not found' }); + } + + // Создаем абсолютный путь к файлу + const filePath = path.resolve(UPLOADS_ROOT, file.storagePath); + + console.log('[BuyProducts] Trying to download file:', { + fileId: file.id, + fileName: file.name, + storagePath: file.storagePath, + absolutePath: filePath, + exists: fs.existsSync(filePath) + }); + + // Проверяем существование файла + if (!fs.existsSync(filePath)) { + console.error('[BuyProducts] File not found on disk:', filePath); + return res.status(404).json({ error: 'File not found on disk' }); + } + + // Устанавливаем правильные заголовки для скачивания с поддержкой кириллицы + const encodedFileName = encodeURIComponent(file.name); + res.setHeader('Content-Type', file.type || 'application/octet-stream'); + res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodedFileName}`); + res.setHeader('Content-Length', file.size); + + // Отправляем файл + res.sendFile(filePath, (err) => { + if (err) { + console.error('[BuyProducts] Error sending file:', err.message); + if (!res.headersSent) { + res.status(500).json({ error: 'Error downloading file' }); + } + } + }); + } catch (error) { + console.error('[BuyProducts] Error downloading file:', error.message); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +module.exports = router; diff --git a/server/routers/procurement/routes/companies.js b/server/routers/procurement/routes/companies.js new file mode 100644 index 0000000..8da9bd5 --- /dev/null +++ b/server/routers/procurement/routes/companies.js @@ -0,0 +1,336 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Company = require('../models/Company'); +const Experience = require('../models/Experience'); +const Request = require('../models/Request'); +const Message = require('../models/Message'); +const mongoose = require('../../../utils/mongoose'); +const { Types } = mongoose; + +// GET /my/info - получить мою компанию (требует авторизации) - ДОЛЖНО быть ПЕРЕД /:id +router.get('/my/info', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const user = await require('../models/User').findById(userId); + + if (!user || !user.companyId) { + return res.status(404).json({ error: 'Company not found' }); + } + + const company = await Company.findById(user.companyId); + + if (!company) { + return res.status(404).json({ error: 'Company not found' }); + } + + res.json({ + ...company.toObject(), + id: company._id + }); + } catch (error) { + console.error('Get my company error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// GET /my/stats - получить статистику компании - ДОЛЖНО быть ПЕРЕД /:id +router.get('/my/stats', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const User = require('../models/User'); + const user = await User.findById(userId); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + let companyId = user.companyId; + + if (!companyId) { + const fallbackCompany = await Company.create({ + fullName: 'Компания пользователя', + shortName: 'Компания пользователя', + verified: false, + partnerGeography: [], + }); + + user.companyId = fallbackCompany._id; + user.updatedAt = new Date(); + await user.save(); + companyId = fallbackCompany._id; + } + + let company = await Company.findById(companyId); + + if (!company) { + company = await Company.create({ + _id: companyId, + fullName: 'Компания пользователя', + verified: false, + partnerGeography: [], + }); + } + + const companyIdString = company._id.toString(); + const companyObjectId = Types.ObjectId.isValid(companyIdString) + ? new Types.ObjectId(companyIdString) + : null; + + const [sentRequests, receivedRequests, unreadMessages] = await Promise.all([ + Request.countDocuments({ senderCompanyId: companyIdString }), + Request.countDocuments({ recipientCompanyId: companyIdString }), + companyObjectId + ? Message.countDocuments({ recipientCompanyId: companyObjectId, read: false }) + : Promise.resolve(0), + ]); + + // Подсчитываем просмотры профиля из запросов к профилю компании + const profileViews = company?.metrics?.profileViews || 0; + + // Получаем статистику за последнюю неделю для изменений + const weekAgo = new Date(); + weekAgo.setDate(weekAgo.getDate() - 7); + + const sentRequestsLastWeek = await Request.countDocuments({ + senderCompanyId: companyIdString, + createdAt: { $gte: weekAgo } + }); + + const receivedRequestsLastWeek = await Request.countDocuments({ + recipientCompanyId: companyIdString, + createdAt: { $gte: weekAgo } + }); + + const stats = { + profileViews: profileViews, + profileViewsChange: 0, // Можно добавить отслеживание просмотров, если нужно + sentRequests, + sentRequestsChange: sentRequestsLastWeek, + receivedRequests, + receivedRequestsChange: receivedRequestsLastWeek, + newMessages: unreadMessages, + rating: Number.isFinite(company?.rating) ? Number(company.rating) : 0, + }; + + res.json(stats); + } catch (error) { + console.error('Get company stats error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// GET /:id/experience - получить опыт компании +router.get('/:id/experience', verifyToken, async (req, res) => { + try { + const { id } = req.params; + + if (!Types.ObjectId.isValid(id)) { + return res.status(400).json({ error: 'Invalid company ID' }); + } + + const experience = await Experience.find({ companyId: new Types.ObjectId(id) }) + .sort({ createdAt: -1 }); + + res.json(experience.map(exp => ({ + ...exp.toObject(), + id: exp._id + }))); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /:id/experience - добавить опыт компании +router.post('/:id/experience', verifyToken, async (req, res) => { + try { + const { id } = req.params; + const { confirmed, customer, subject, volume, contact, comment } = req.body; + + if (!Types.ObjectId.isValid(id)) { + return res.status(400).json({ error: 'Invalid company ID' }); + } + + const newExp = await Experience.create({ + companyId: new Types.ObjectId(id), + confirmed: confirmed || false, + customer: customer || '', + subject: subject || '', + volume: volume || '', + contact: contact || '', + comment: comment || '' + }); + + res.status(201).json({ + ...newExp.toObject(), + id: newExp._id + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PUT /:id/experience/:expId - обновить опыт +router.put('/:id/experience/:expId', verifyToken, async (req, res) => { + try { + const { id, expId } = req.params; + + if (!Types.ObjectId.isValid(id) || !Types.ObjectId.isValid(expId)) { + return res.status(400).json({ error: 'Invalid IDs' }); + } + + const experience = await Experience.findByIdAndUpdate( + new Types.ObjectId(expId), + { + ...req.body, + updatedAt: new Date() + }, + { new: true } + ); + + if (!experience || experience.companyId.toString() !== id) { + return res.status(404).json({ error: 'Experience not found' }); + } + + res.json({ + ...experience.toObject(), + id: experience._id + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /:id/experience/:expId - удалить опыт +router.delete('/:id/experience/:expId', verifyToken, async (req, res) => { + try { + const { id, expId } = req.params; + + if (!Types.ObjectId.isValid(id) || !Types.ObjectId.isValid(expId)) { + return res.status(400).json({ error: 'Invalid IDs' }); + } + + const experience = await Experience.findById(new Types.ObjectId(expId)); + + if (!experience || experience.companyId.toString() !== id) { + return res.status(404).json({ error: 'Experience not found' }); + } + + await Experience.findByIdAndDelete(new Types.ObjectId(expId)); + res.json({ message: 'Experience deleted' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Получить компанию по ID (ДОЛЖНО быть ПОСЛЕ специфичных маршрутов) +router.get('/:id', async (req, res) => { + try { + const company = await Company.findById(req.params.id); + + if (!company) { + if (!Types.ObjectId.isValid(req.params.id)) { + return res.status(404).json({ error: 'Company not found' }); + } + + const placeholder = await Company.create({ + _id: new Types.ObjectId(req.params.id), + fullName: 'Новая компания', + shortName: 'Новая компания', + verified: false, + partnerGeography: [], + industry: '', + companySize: '', + }); + + return res.json({ + ...placeholder.toObject(), + id: placeholder._id, + }); + } + + // Отслеживаем просмотр профиля (если это не владелец компании) + const userId = req.userId; + if (userId) { + const User = require('../models/User'); + const user = await User.findById(userId); + if (user && user.companyId && user.companyId.toString() !== company._id.toString()) { + // Инкрементируем просмотры профиля + if (!company.metrics) { + company.metrics = {}; + } + if (!company.metrics.profileViews) { + company.metrics.profileViews = 0; + } + company.metrics.profileViews = (company.metrics.profileViews || 0) + 1; + await company.save(); + } + } + + res.json({ + ...company.toObject(), + id: company._id + }); + } catch (error) { + console.error('Get company error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Обновить компанию (требует авторизации) +const updateCompanyHandler = async (req, res) => { + try { + const company = await Company.findByIdAndUpdate( + req.params.id, + { ...req.body, updatedAt: new Date() }, + { new: true } + ); + + if (!company) { + return res.status(404).json({ error: 'Company not found' }); + } + + res.json({ + ...company.toObject(), + id: company._id + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +router.put('/:id', verifyToken, updateCompanyHandler); +router.patch('/:id', verifyToken, updateCompanyHandler); + +// Поиск с AI анализом +router.post('/ai-search', async (req, res) => { + try { + const { query } = req.body; + + if (!query) { + return res.status(400).json({ error: 'Query required' }); + } + + const q = query.toLowerCase(); + const result = await Company.find({ + $or: [ + { fullName: { $regex: q, $options: 'i' } }, + { shortName: { $regex: q, $options: 'i' } }, + { industry: { $regex: q, $options: 'i' } } + ] + }); + + res.json({ + companies: result.map(c => ({ + ...c.toObject(), + id: c._id + })), + total: result.length, + aiSuggestion: `Found ${result.length} companies matching "${query}"` + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/server/routers/procurement/routes/experience.js b/server/routers/procurement/routes/experience.js new file mode 100644 index 0000000..47a2d27 --- /dev/null +++ b/server/routers/procurement/routes/experience.js @@ -0,0 +1,134 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Experience = require('../models/Experience'); +const mongoose = require('../../../utils/mongoose'); +const { Types } = mongoose; + +// GET /experience - Получить список опыта работы компании +router.get('/', verifyToken, async (req, res) => { + try { + const { companyId } = req.query; + + if (!companyId) { + return res.status(400).json({ error: 'companyId is required' }); + } + + if (!Types.ObjectId.isValid(companyId)) { + return res.status(400).json({ error: 'Invalid company ID' }); + } + + const companyExperiences = await Experience.find({ + companyId: new Types.ObjectId(companyId) + }).sort({ createdAt: -1 }); + + res.json(companyExperiences.map(exp => ({ + ...exp.toObject(), + id: exp._id + }))); + } catch (error) { + console.error('Get experience error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// POST /experience - Создать запись опыта работы +router.post('/', verifyToken, async (req, res) => { + try { + const { companyId, data } = req.body; + + if (!companyId || !data) { + return res.status(400).json({ error: 'companyId and data are required' }); + } + + if (!Types.ObjectId.isValid(companyId)) { + return res.status(400).json({ error: 'Invalid company ID' }); + } + + const { confirmed, customer, subject, volume, contact, comment } = data; + + if (!customer || !subject) { + return res.status(400).json({ error: 'customer and subject are required' }); + } + + const newExperience = await Experience.create({ + companyId: new Types.ObjectId(companyId), + confirmed: confirmed || false, + customer, + subject, + volume: volume || '', + contact: contact || '', + comment: comment || '' + }); + + res.status(201).json({ + ...newExperience.toObject(), + id: newExperience._id + }); + } catch (error) { + console.error('Create experience error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// PUT /experience/:id - Обновить запись опыта работы +router.put('/:id', verifyToken, async (req, res) => { + try { + const { id } = req.params; + const { data } = req.body; + + if (!data) { + return res.status(400).json({ error: 'data is required' }); + } + + if (!Types.ObjectId.isValid(id)) { + return res.status(400).json({ error: 'Invalid experience ID' }); + } + + const updatedExperience = await Experience.findByIdAndUpdate( + new Types.ObjectId(id), + { + ...data, + updatedAt: new Date() + }, + { new: true } + ); + + if (!updatedExperience) { + return res.status(404).json({ error: 'Experience not found' }); + } + + res.json({ + ...updatedExperience.toObject(), + id: updatedExperience._id + }); + } catch (error) { + console.error('Update experience error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// DELETE /experience/:id - Удалить запись опыта работы +router.delete('/:id', verifyToken, async (req, res) => { + try { + const { id } = req.params; + + if (!Types.ObjectId.isValid(id)) { + return res.status(400).json({ error: 'Invalid experience ID' }); + } + + const deletedExperience = await Experience.findByIdAndDelete(new Types.ObjectId(id)); + + if (!deletedExperience) { + return res.status(404).json({ error: 'Experience not found' }); + } + + res.json({ message: 'Experience deleted successfully' }); + } catch (error) { + console.error('Delete experience error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +module.exports = router; + diff --git a/server/routers/procurement/routes/home.js b/server/routers/procurement/routes/home.js new file mode 100644 index 0000000..3914a31 --- /dev/null +++ b/server/routers/procurement/routes/home.js @@ -0,0 +1,137 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const BuyProduct = require('../models/BuyProduct'); +const Request = require('../models/Request'); + +// Получить агрегированные данные для главной страницы +router.get('/aggregates', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const User = require('../models/User'); + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.json({ + docsCount: 0, + acceptsCount: 0, + requestsCount: 0 + }); + } + + const companyId = user.companyId.toString(); + + // Получить все BuyProduct для подсчета файлов и акцептов + const buyProducts = await BuyProduct.find({ companyId }); + + // Подсчет документов - сумма всех файлов во всех BuyProduct + const docsCount = buyProducts.reduce((total, product) => { + return total + (product.files ? product.files.length : 0); + }, 0); + + // Подсчет акцептов - сумма всех acceptedBy во всех BuyProduct + const acceptsCount = buyProducts.reduce((total, product) => { + return total + (product.acceptedBy ? product.acceptedBy.length : 0); + }, 0); + + // Подсчет исходящих запросов (только отправленные этой компанией) + const requestsCount = await Request.countDocuments({ + senderCompanyId: companyId + }); + + res.json({ + docsCount, + acceptsCount, + requestsCount + }); + } catch (error) { + console.error('Error getting aggregates:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Получить статистику компании +router.get('/stats', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const User = require('../models/User'); + const Company = require('../models/Company'); + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.json({ + profileViews: 0, + profileViewsChange: 0, + sentRequests: 0, + sentRequestsChange: 0, + receivedRequests: 0, + receivedRequestsChange: 0, + newMessages: 0, + rating: 0 + }); + } + + const companyId = user.companyId.toString(); + const company = await Company.findById(user.companyId); + + const sentRequests = await Request.countDocuments({ senderCompanyId: companyId }); + const receivedRequests = await Request.countDocuments({ recipientCompanyId: companyId }); + + res.json({ + profileViews: company?.metrics?.profileViews || 0, + profileViewsChange: 0, + sentRequests, + sentRequestsChange: 0, + receivedRequests, + receivedRequestsChange: 0, + newMessages: 0, + rating: company?.rating || 0 + }); + } catch (error) { + console.error('Error getting stats:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Получить рекомендации партнеров (AI) +router.get('/recommendations', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const User = require('../models/User'); + const Company = require('../models/Company'); + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.json({ + recommendations: [], + message: 'No recommendations available' + }); + } + + // Получить компании кроме текущей + const companies = await Company.find({ + _id: { $ne: user.companyId } + }) + .sort({ rating: -1 }) + .limit(5); + + const recommendations = companies.map(company => ({ + id: company._id.toString(), + name: company.fullName || company.shortName, + industry: company.industry, + logo: company.logo, + matchScore: company.rating ? Math.min(100, Math.round(company.rating * 20)) : 50, + reason: 'Matches your industry' + })); + + res.json({ + recommendations, + message: recommendations.length > 0 ? 'Recommendations available' : 'No recommendations available' + }); + } catch (error) { + console.error('Error getting recommendations:', error); + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/server/routers/procurement/routes/messages.js b/server/routers/procurement/routes/messages.js new file mode 100644 index 0000000..61766ac --- /dev/null +++ b/server/routers/procurement/routes/messages.js @@ -0,0 +1,263 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Message = require('../models/Message'); +const mongoose = require('../../../utils/mongoose'); +const { ObjectId } = mongoose.Types; + +// Функция для логирования с проверкой DEV переменной +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +// GET /messages/threads - получить все потоки для компании +router.get('/threads', verifyToken, async (req, res) => { + try { + const companyId = req.companyId; + + log('[Messages] Fetching threads for companyId:', companyId, 'type:', typeof companyId); + + // Преобразовать в ObjectId если это строка + let companyObjectId = companyId; + let companyIdString = companyId.toString ? companyId.toString() : companyId; + + try { + if (typeof companyId === 'string' && ObjectId.isValid(companyId)) { + companyObjectId = new ObjectId(companyId); + } + } catch (e) { + log('[Messages] Could not convert to ObjectId:', e.message); + } + + log('[Messages] Using companyObjectId:', companyObjectId, 'companyIdString:', companyIdString); + + // Получить все сообщения где текущая компания отправитель или получатель + // Поддерживаем оба формата - ObjectId и строки + const allMessages = await Message.find({ + $or: [ + { senderCompanyId: companyObjectId }, + { senderCompanyId: companyIdString }, + { recipientCompanyId: companyObjectId }, + { recipientCompanyId: companyIdString }, + // Также ищем по threadId который может содержать ID компании + { threadId: { $regex: companyIdString } } + ] + }) + .sort({ timestamp: -1 }) + .limit(500); + + log('[Messages] Found', allMessages.length, 'messages for company'); + + if (allMessages.length === 0) { + log('[Messages] No messages found'); + res.json([]); + return; + } + + // Группируем по потокам и берем последнее сообщение каждого потока + const threadsMap = new Map(); + allMessages.forEach(msg => { + const threadId = msg.threadId; + if (!threadsMap.has(threadId)) { + threadsMap.set(threadId, { + threadId, + lastMessage: msg.text, + lastMessageAt: msg.timestamp, + senderCompanyId: msg.senderCompanyId, + recipientCompanyId: msg.recipientCompanyId + }); + } + }); + + const threads = Array.from(threadsMap.values()).sort((a, b) => + new Date(b.lastMessageAt) - new Date(a.lastMessageAt) + ); + + log('[Messages] Returned', threads.length, 'unique threads'); + + res.json(threads); + } catch (error) { + console.error('[Messages] Error fetching threads:', error.message, error.stack); + res.status(500).json({ error: error.message }); + } +}); + +// GET /messages/:threadId - получить сообщения потока +router.get('/:threadId', verifyToken, async (req, res) => { + try { + const { threadId } = req.params; + const companyId = req.companyId; + + // Получить все сообщения потока + const threadMessages = await Message.find({ threadId }) + .sort({ timestamp: 1 }) + .exec(); + + // Отметить сообщения как прочитанные для текущей компании + await Message.updateMany( + { threadId, recipientCompanyId: companyId, read: false }, + { read: true } + ); + + log('[Messages] Returned', threadMessages.length, 'messages for thread', threadId); + + res.json(threadMessages); + } catch (error) { + console.error('[Messages] Error fetching messages:', error.message); + res.status(500).json({ error: error.message }); + } +}); + +// POST /messages/:threadId - добавить сообщение в поток +router.post('/:threadId', verifyToken, async (req, res) => { + try { + const { threadId } = req.params; + const { text, senderCompanyId } = req.body; + + if (!text || !threadId) { + return res.status(400).json({ error: 'Text and threadId required' }); + } + + // Определить получателя на основе threadId + // threadId формат: "thread-id1-id2" + const threadParts = threadId.replace('thread-', '').split('-'); + let recipientCompanyId = null; + + const currentSender = senderCompanyId || req.companyId; + const currentSenderString = currentSender.toString ? currentSender.toString() : currentSender; + + if (threadParts.length >= 2) { + const companyId1 = threadParts[0]; + const companyId2 = threadParts[1]; + // Получатель - это другая сторона + recipientCompanyId = currentSenderString === companyId1 ? companyId2 : companyId1; + } + + log('[Messages] POST /messages/:threadId'); + log('[Messages] threadId:', threadId); + log('[Messages] Sender:', currentSender); + log('[Messages] SenderString:', currentSenderString); + log('[Messages] Recipient:', recipientCompanyId); + + // Найти recipientCompanyId по ObjectId если нужно + let recipientObjectId = recipientCompanyId; + try { + if (typeof recipientCompanyId === 'string' && ObjectId.isValid(recipientCompanyId)) { + recipientObjectId = new ObjectId(recipientCompanyId); + } + } catch (e) { + log('[Messages] Could not convert recipientId to ObjectId'); + } + + const message = new Message({ + threadId, + senderCompanyId: currentSender, + recipientCompanyId: recipientObjectId, + text: text.trim(), + read: false, + timestamp: new Date() + }); + + const savedMessage = await message.save(); + + log('[Messages] New message created:', savedMessage._id); + log('[Messages] Message data:', { + threadId: savedMessage.threadId, + senderCompanyId: savedMessage.senderCompanyId, + recipientCompanyId: savedMessage.recipientCompanyId + }); + + res.status(201).json(savedMessage); + } catch (error) { + console.error('[Messages] Error creating message:', error.message, error.stack); + res.status(500).json({ error: error.message }); + } +}); + +// MIGRATION ENDPOINT - Fix recipientCompanyId for all messages +router.post('/admin/migrate-fix-recipients', async (req, res) => { + try { + const allMessages = await Message.find().exec(); + log('[Messages] Migrating', allMessages.length, 'messages...'); + + let fixedCount = 0; + let errorCount = 0; + + for (const message of allMessages) { + try { + const threadId = message.threadId; + if (!threadId) continue; + + // Parse threadId формат "thread-id1-id2" или "id1-id2" + const ids = threadId.replace('thread-', '').split('-'); + if (ids.length < 2) { + errorCount++; + continue; + } + + const companyId1 = ids[0]; + const companyId2 = ids[1]; + + // Compare with senderCompanyId + const senderIdString = message.senderCompanyId.toString ? message.senderCompanyId.toString() : message.senderCompanyId; + const expectedRecipient = senderIdString === companyId1 ? companyId2 : companyId1; + + // If recipientCompanyId is not set or wrong - fix it + if (!message.recipientCompanyId || message.recipientCompanyId.toString() !== expectedRecipient) { + let recipientObjectId = expectedRecipient; + try { + if (typeof expectedRecipient === 'string' && ObjectId.isValid(expectedRecipient)) { + recipientObjectId = new ObjectId(expectedRecipient); + } + } catch (e) { + // continue + } + + await Message.updateOne( + { _id: message._id }, + { recipientCompanyId: recipientObjectId } + ); + + fixedCount++; + } + } catch (err) { + console.error('[Messages] Migration error:', err.message); + errorCount++; + } + } + + log('[Messages] Migration completed! Fixed:', fixedCount, 'Errors:', errorCount); + res.json({ success: true, fixed: fixedCount, errors: errorCount, total: allMessages.length }); + } catch (error) { + console.error('[Messages] Migration error:', error.message); + res.status(500).json({ error: error.message }); + } +}); + +// DEBUG ENDPOINT +router.get('/debug/all-messages', async (req, res) => { + try { + const allMessages = await Message.find().limit(10).exec(); + log('[Debug] Total messages in DB:', allMessages.length); + + const info = allMessages.map(m => ({ + _id: m._id, + threadId: m.threadId, + senderCompanyId: m.senderCompanyId?.toString ? m.senderCompanyId.toString() : m.senderCompanyId, + recipientCompanyId: m.recipientCompanyId?.toString ? m.recipientCompanyId.toString() : m.recipientCompanyId, + text: m.text.substring(0, 30) + })); + + res.json({ totalCount: allMessages.length, messages: info }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/server/routers/procurement/routes/products.js b/server/routers/procurement/routes/products.js new file mode 100644 index 0000000..9a09aaf --- /dev/null +++ b/server/routers/procurement/routes/products.js @@ -0,0 +1,175 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Product = require('../models/Product'); + +// Функция для логирования с проверкой DEV переменной +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +// Helper to transform _id to id +const transformProduct = (doc) => { + if (!doc) return null; + const obj = doc.toObject ? doc.toObject() : doc; + return { + ...obj, + id: obj._id, + _id: undefined + }; +}; + +// GET /products - Получить список продуктов/услуг компании (текущего пользователя) +router.get('/', verifyToken, async (req, res) => { + try { + const companyId = req.companyId; + + log('[Products] GET Fetching products for companyId:', companyId); + + const products = await Product.find({ companyId }) + .sort({ createdAt: -1 }) + .exec(); + + log('[Products] Found', products.length, 'products'); + res.json(products.map(transformProduct)); + } catch (error) { + console.error('[Products] Get error:', error.message); + res.status(500).json({ error: 'Internal server error', message: error.message }); + } +}); + +// POST /products - Создать продукт/услугу +router.post('/', verifyToken, async (req, res) => { + // try { + const { name, category, description, type, productUrl, price, unit, minOrder } = req.body; + const companyId = req.companyId; + + log('[Products] POST Creating product:', { name, category, type }); + + // // Валидация + // if (!name || !category || !description || !type) { + // return res.status(400).json({ error: 'name, category, description, and type are required' }); + // } + + // if (description.length < 20) { + // return res.status(400).json({ error: 'Description must be at least 20 characters' }); + // } + + const newProduct = new Product({ + name: name.trim(), + category: category.trim(), + description: description.trim(), + type, + productUrl: productUrl || '', + companyId, + price: price || '', + unit: unit || '', + minOrder: minOrder || '' + }); + + const savedProduct = await newProduct.save(); + log('[Products] Product created with ID:', savedProduct._id); + + res.status(201).json(transformProduct(savedProduct)); + // } catch (error) { + // console.error('[Products] Create error:', error.message); + // res.status(500).json({ error: 'Internal server error', message: error.message }); + // } +}); + +// PUT /products/:id - Обновить продукт/услугу +router.put('/:id', verifyToken, async (req, res) => { + try { + const { id } = req.params; + const updates = req.body; + const companyId = req.companyId; + + const product = await Product.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + // Проверить, что продукт принадлежит текущей компании + if (product.companyId !== companyId) { + return res.status(403).json({ error: 'Not authorized' }); + } + + const updatedProduct = await Product.findByIdAndUpdate( + id, + { ...updates, updatedAt: new Date() }, + { new: true, runValidators: true } + ); + + log('[Products] Product updated:', id); + res.json(transformProduct(updatedProduct)); + } catch (error) { + console.error('[Products] Update error:', error.message); + res.status(500).json({ error: 'Internal server error', message: error.message }); + } +}); + +// PATCH /products/:id - Частичное обновление продукта/услуги +router.patch('/:id', verifyToken, async (req, res) => { + try { + const { id } = req.params; + const updates = req.body; + const companyId = req.companyId; + + const product = await Product.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + if (product.companyId !== companyId) { + return res.status(403).json({ error: 'Not authorized' }); + } + + const updatedProduct = await Product.findByIdAndUpdate( + id, + { ...updates, updatedAt: new Date() }, + { new: true, runValidators: true } + ); + + log('[Products] Product patched:', id); + res.json(transformProduct(updatedProduct)); + } catch (error) { + console.error('[Products] Patch error:', error.message); + res.status(500).json({ error: 'Internal server error', message: error.message }); + } +}); + +// DELETE /products/:id - Удалить продукт/услугу +router.delete('/:id', verifyToken, async (req, res) => { + try { + const { id } = req.params; + const companyId = req.companyId; + + const product = await Product.findById(id); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + if (product.companyId !== companyId) { + return res.status(403).json({ error: 'Not authorized' }); + } + + await Product.findByIdAndDelete(id); + + log('[Products] Product deleted:', id); + res.json({ message: 'Product deleted successfully' }); + } catch (error) { + console.error('[Products] Delete error:', error.message); + res.status(500).json({ error: 'Internal server error', message: error.message }); + } +}); + +module.exports = router; diff --git a/server/routers/procurement/routes/requests.js b/server/routers/procurement/routes/requests.js new file mode 100644 index 0000000..93c072b --- /dev/null +++ b/server/routers/procurement/routes/requests.js @@ -0,0 +1,563 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Request = require('../models/Request'); +const BuyProduct = require('../models/BuyProduct'); +const path = require('path'); +const fs = require('fs'); +const multer = require('multer'); +const mongoose = require('../../../utils/mongoose'); + +// Функция для логирования с проверкой DEV переменной +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +const REQUESTS_UPLOAD_ROOT = 'server/routers/remote-assets/uploads/requests'; + +const ensureDirectory = (dirPath) => { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +}; + +ensureDirectory(REQUESTS_UPLOAD_ROOT); + +const MAX_REQUEST_FILE_SIZE = 20 * 1024 * 1024; // 20MB +const ALLOWED_REQUEST_MIME_TYPES = new Set([ + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'text/csv', +]); + +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + const subfolder = req.requestUploadSubfolder || ''; + const destinationDir = `${REQUESTS_UPLOAD_ROOT}/${subfolder}`; + ensureDirectory(destinationDir); + cb(null, destinationDir); + }, + filename: (req, file, cb) => { + const extension = path.extname(file.originalname) || ''; + const baseName = path + .basename(file.originalname, extension) + .replace(/[^a-zA-Z0-9-_]+/g, '_') + .toLowerCase(); + cb(null, `${Date.now()}_${baseName}${extension}`); + }, +}); + +const upload = multer({ + storage, + limits: { + fileSize: MAX_REQUEST_FILE_SIZE, + }, + fileFilter: (req, file, cb) => { + if (ALLOWED_REQUEST_MIME_TYPES.has(file.mimetype)) { + cb(null, true); + return; + } + + if (!req.invalidFiles) { + req.invalidFiles = []; + } + req.invalidFiles.push(file.originalname); + cb(null, false); + }, +}); + +const handleFilesUpload = (fieldName, subfolderResolver, maxCount = 10) => (req, res, next) => { + req.invalidFiles = []; + req.requestUploadSubfolder = subfolderResolver(req); + + upload.array(fieldName, maxCount)(req, res, (err) => { + if (err) { + console.error('[Requests] Multer error:', err.message); + if (err.code === 'LIMIT_FILE_SIZE') { + return res.status(400).json({ error: 'File is too large. Maximum size is 20MB.' }); + } + return res.status(400).json({ error: err.message }); + } + next(); + }); +}; + +const cleanupUploadedFiles = async (req) => { + if (!Array.isArray(req.files) || req.files.length === 0) { + return; + } + + const subfolder = req.requestUploadSubfolder || ''; + const removalTasks = req.files.map((file) => { + const filePath = `${REQUESTS_UPLOAD_ROOT}/${subfolder}/${file.filename}`; + return fs.promises.unlink(filePath).catch((error) => { + if (error.code !== 'ENOENT') { + console.error('[Requests] Failed to cleanup uploaded file:', error.message); + } + }); + }); + + await Promise.all(removalTasks); +}; + +const mapFilesToMetadata = (req) => { + if (!Array.isArray(req.files) || req.files.length === 0) { + return []; + } + + const subfolder = req.requestUploadSubfolder || ''; + return req.files.map((file) => { + const relativePath = `requests/${subfolder}/${file.filename}`; + return { + id: `file-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + name: file.originalname, + url: `/uploads/${relativePath}`, + type: file.mimetype, + size: file.size, + uploadedAt: new Date(), + storagePath: relativePath, + }; + }); +}; + +const normalizeToArray = (value) => { + if (!value) { + return []; + } + if (Array.isArray(value)) { + return value; + } + + try { + const parsed = JSON.parse(value); + if (Array.isArray(parsed)) { + return parsed; + } + } catch (error) { + // ignore JSON parse errors + } + + return String(value) + .split(',') + .map((item) => item.trim()) + .filter(Boolean); +}; + +const removeStoredFiles = async (files = []) => { + if (!files || files.length === 0) { + return; + } + + const tasks = files + .filter((file) => file && file.storagePath) + .map((file) => { + const absolutePath = `server/routers/remote-assets/uploads/${file.storagePath}`; + return fs.promises.unlink(absolutePath).catch((error) => { + if (error.code !== 'ENOENT') { + console.error('[Requests] Failed to remove stored file:', error.message); + } + }); + }); + + await Promise.all(tasks); +}; + +// GET /requests/sent - получить отправленные запросы +router.get('/sent', verifyToken, async (req, res) => { + try { + const companyId = req.companyId; + + if (!companyId) { + return res.status(400).json({ error: 'Company ID is required' }); + } + + const requests = await Request.find({ senderCompanyId: companyId }) + .sort({ createdAt: -1 }) + .exec(); + + log('[Requests] Returned', requests.length, 'sent requests for company', companyId); + + res.json(requests); + } catch (error) { + console.error('[Requests] Error fetching sent requests:', error.message); + res.status(500).json({ error: error.message }); + } +}); + +// GET /requests/received - получить полученные запросы +router.get('/received', verifyToken, async (req, res) => { + try { + const companyId = req.companyId; + + if (!companyId) { + return res.status(400).json({ error: 'Company ID is required' }); + } + + const requests = await Request.find({ recipientCompanyId: companyId }) + .sort({ createdAt: -1 }) + .exec(); + + log('[Requests] Returned', requests.length, 'received requests for company', companyId); + + res.json(requests); + } catch (error) { + console.error('[Requests] Error fetching received requests:', error.message); + res.status(500).json({ error: error.message }); + } +}); + +// POST /requests - создать запрос +router.post( + '/', + verifyToken, + handleFilesUpload('files', (req) => `sent/${(req.companyId || 'unknown').toString()}`, 10), + async (req, res) => { + try { + const senderCompanyId = req.companyId; + const recipients = normalizeToArray(req.body.recipientCompanyIds); + const text = (req.body.text || '').trim(); + const productId = req.body.productId ? String(req.body.productId) : null; + let subject = (req.body.subject || '').trim(); + + if (req.invalidFiles && req.invalidFiles.length > 0) { + await cleanupUploadedFiles(req); + return res.status(400).json({ + error: 'Unsupported file type. Allowed formats: PDF, DOC, DOCX, XLS, XLSX, CSV.', + details: req.invalidFiles, + }); + } + + if (!text) { + await cleanupUploadedFiles(req); + return res.status(400).json({ error: 'Request text is required' }); + } + + if (!recipients.length) { + await cleanupUploadedFiles(req); + return res.status(400).json({ error: 'At least one recipient is required' }); + } + + let uploadedFiles = mapFilesToMetadata(req); + + console.log('========================'); + console.log('[Requests] Initial uploadedFiles:', uploadedFiles.length); + console.log('[Requests] ProductId:', productId); + + // Если есть productId, получаем данные товара + if (productId) { + try { + const product = await BuyProduct.findById(productId); + console.log('[Requests] Product found:', product ? product.name : 'null'); + console.log('[Requests] Product files count:', product?.files?.length || 0); + if (product && product.files) { + console.log('[Requests] Product files:', JSON.stringify(product.files, null, 2)); + } + + if (product) { + // Берем subject из товара, если не указан + if (!subject) { + subject = product.name; + } + + // Если файлы не загружены вручную, используем файлы из товара + if (uploadedFiles.length === 0 && product.files && product.files.length > 0) { + console.log('[Requests] ✅ Copying files from product...'); + // Копируем файлы из товара, изменяя путь для запроса + uploadedFiles = product.files.map(file => ({ + id: file.id || `file-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + name: file.name, + url: file.url, + type: file.type, + size: file.size, + uploadedAt: file.uploadedAt || new Date(), + storagePath: file.storagePath || file.url.replace('/uploads/', ''), + })); + console.log('[Requests] ✅ Using', uploadedFiles.length, 'files from product:', productId); + console.log('[Requests] ✅ Copied files:', JSON.stringify(uploadedFiles, null, 2)); + } else { + console.log('[Requests] ❌ NOT copying files. uploadedFiles.length:', uploadedFiles.length, 'product.files.length:', product.files?.length || 0); + } + } + } catch (lookupError) { + console.error('[Requests] ❌ Failed to lookup product:', lookupError.message); + console.error(lookupError.stack); + } + } + + console.log('[Requests] Final uploadedFiles for saving:', JSON.stringify(uploadedFiles, null, 2)); + console.log('========================'); + + if (!subject) { + await cleanupUploadedFiles(req); + return res.status(400).json({ error: 'Subject is required' }); + } + + const results = []; + for (const recipientCompanyId of recipients) { + try { + const request = new Request({ + senderCompanyId, + recipientCompanyId, + text, + productId, + subject, + files: uploadedFiles, + responseFiles: [], + status: 'pending', + }); + + await request.save(); + results.push({ + companyId: recipientCompanyId, + success: true, + message: 'Request sent successfully', + }); + + log('[Requests] Request sent to company:', recipientCompanyId); + } catch (err) { + console.error('[Requests] Error storing request for company:', recipientCompanyId, err.message); + results.push({ + companyId: recipientCompanyId, + success: false, + message: err.message, + }); + } + } + + const createdAt = new Date(); + + res.status(201).json({ + id: 'bulk-' + Date.now(), + text, + subject, + productId, + files: uploadedFiles, + result: results, + createdAt, + }); + } catch (error) { + console.error('[Requests] Error creating request:', error.message); + res.status(500).json({ error: error.message }); + } + } +); + +// PUT /requests/:id - ответить на запрос +router.put( + '/:id', + verifyToken, + handleFilesUpload('responseFiles', (req) => `responses/${req.params.id || 'unknown'}`, 5), + async (req, res) => { + try { + const { id } = req.params; + console.log('[Requests] PUT /requests/:id called with id:', id); + console.log('[Requests] Request body:', req.body); + console.log('[Requests] Files:', req.files); + console.log('[Requests] CompanyId:', req.companyId); + + const responseText = (req.body.response || '').trim(); + const statusRaw = (req.body.status || 'accepted').toLowerCase(); + const status = statusRaw === 'rejected' ? 'rejected' : 'accepted'; + + console.log('[Requests] Response text:', responseText); + console.log('[Requests] Status:', status); + + if (req.invalidFiles && req.invalidFiles.length > 0) { + await cleanupUploadedFiles(req); + return res.status(400).json({ + error: 'Unsupported file type. Allowed formats: PDF, DOC, DOCX, XLS, XLSX, CSV.', + details: req.invalidFiles, + }); + } + + if (!responseText) { + await cleanupUploadedFiles(req); + return res.status(400).json({ error: 'Response text is required' }); + } + + const request = await Request.findById(id); + + if (!request) { + await cleanupUploadedFiles(req); + return res.status(404).json({ error: 'Request not found' }); + } + + if (request.recipientCompanyId !== req.companyId) { + await cleanupUploadedFiles(req); + return res.status(403).json({ error: 'Not authorized' }); + } + + const uploadedResponseFiles = mapFilesToMetadata(req); + console.log('[Requests] Uploaded response files count:', uploadedResponseFiles.length); + console.log('[Requests] Uploaded response files:', JSON.stringify(uploadedResponseFiles, null, 2)); + + if (uploadedResponseFiles.length > 0) { + await removeStoredFiles(request.responseFiles || []); + request.responseFiles = uploadedResponseFiles; + } + + request.response = responseText; + request.status = status; + request.respondedAt = new Date(); + request.updatedAt = new Date(); + + let savedRequest; + try { + savedRequest = await request.save(); + log('[Requests] Request responded:', id); + } catch (saveError) { + console.error('[Requests] Mongoose save failed, trying direct MongoDB update:', saveError.message); + // Fallback: использовать MongoDB драйвер напрямую + const updateData = { + response: responseText, + status: status, + respondedAt: new Date(), + updatedAt: new Date() + }; + if (uploadedResponseFiles.length > 0) { + updateData.responseFiles = uploadedResponseFiles; + } + + const result = await mongoose.connection.collection('requests').findOneAndUpdate( + { _id: new mongoose.Types.ObjectId(id) }, + { $set: updateData }, + { returnDocument: 'after' } + ); + + if (!result) { + throw new Error('Failed to update request'); + } + savedRequest = result; + log('[Requests] Request responded via direct MongoDB update:', id); + } + + res.json(savedRequest); + } catch (error) { + console.error('[Requests] Error responding to request:', error.message); + console.error('[Requests] Error stack:', error.stack); + if (error.name === 'ValidationError') { + console.error('[Requests] Validation errors:', JSON.stringify(error.errors, null, 2)); + } + res.status(500).json({ error: error.message }); + } + } +); + +// GET /requests/download/:id/:fileId - скачать файл ответа +router.get('/download/:id/:fileId', verifyToken, async (req, res) => { + try { + console.log('[Requests] Download request received:', { + requestId: req.params.id, + fileId: req.params.fileId, + userId: req.userId, + companyId: req.companyId, + }); + + const { id, fileId } = req.params; + const request = await Request.findById(id); + + if (!request) { + return res.status(404).json({ error: 'Request not found' }); + } + + // Проверяем, что пользователь имеет доступ к запросу (отправитель или получатель) + if (request.senderCompanyId !== req.companyId && request.recipientCompanyId !== req.companyId) { + return res.status(403).json({ error: 'Not authorized' }); + } + + // Ищем файл в responseFiles или в обычных files + let file = request.responseFiles?.find((f) => f.id === fileId); + if (!file) { + file = request.files?.find((f) => f.id === fileId); + } + if (!file) { + return res.status(404).json({ error: 'File not found' }); + } + + // Создаем абсолютный путь к файлу + // Если storagePath не начинается с 'requests/', значит это файл из buy-products + let fullPath = file.storagePath; + if (!fullPath.startsWith('requests/')) { + fullPath = `buy-products/${fullPath}`; + } + const filePath = path.resolve(`server/routers/remote-assets/uploads/${fullPath}`); + + console.log('[Requests] Trying to download file:', { + fileId: file.id, + fileName: file.name, + storagePath: file.storagePath, + absolutePath: filePath, + exists: fs.existsSync(filePath), + }); + + // Проверяем существование файла + if (!fs.existsSync(filePath)) { + console.error('[Requests] File not found on disk:', filePath); + return res.status(404).json({ error: 'File not found on disk' }); + } + + // Устанавливаем правильные заголовки для скачивания с поддержкой кириллицы + const encodedFileName = encodeURIComponent(file.name); + res.setHeader('Content-Type', file.type || 'application/octet-stream'); + res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodedFileName}`); + res.setHeader('Content-Length', file.size); + + // Отправляем файл + res.sendFile(filePath, (err) => { + if (err) { + console.error('[Requests] Error sending file:', err.message); + if (!res.headersSent) { + res.status(500).json({ error: 'Error sending file' }); + } + } else { + log('[Requests] File downloaded:', file.name); + } + }); + } catch (error) { + console.error('[Requests] Error downloading file:', error.message); + if (!res.headersSent) { + res.status(500).json({ error: error.message }); + } + } +}); + +// DELETE /requests/:id - удалить запрос +router.delete('/:id', verifyToken, async (req, res) => { + try { + const { id } = req.params; + + const request = await Request.findById(id); + + if (!request) { + return res.status(404).json({ error: 'Request not found' }); + } + + // Может удалить отправитель или получатель + if (request.senderCompanyId !== req.companyId && request.recipientCompanyId !== req.companyId) { + return res.status(403).json({ error: 'Not authorized' }); + } + + await removeStoredFiles(request.files || []); + await removeStoredFiles(request.responseFiles || []); + + await Request.findByIdAndDelete(id); + + log('[Requests] Request deleted:', id); + + res.json({ message: 'Request deleted successfully' }); + } catch (error) { + console.error('[Requests] Error deleting request:', error.message); + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/server/routers/procurement/routes/reviews.js b/server/routers/procurement/routes/reviews.js new file mode 100644 index 0000000..a3740ed --- /dev/null +++ b/server/routers/procurement/routes/reviews.js @@ -0,0 +1,145 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Review = require('../models/Review'); +const Company = require('../models/Company'); + +// Функция для логирования с проверкой DEV переменной +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +// Функция для пересчета рейтинга компании +const updateCompanyRating = async (companyId) => { + try { + const reviews = await Review.find({ companyId }); + + if (reviews.length === 0) { + await Company.findByIdAndUpdate(companyId, { + rating: 0, + reviews: 0, + updatedAt: new Date() + }); + return; + } + + const totalRating = reviews.reduce((sum, review) => sum + review.rating, 0); + const averageRating = totalRating / reviews.length; + + await Company.findByIdAndUpdate(companyId, { + rating: averageRating, + reviews: reviews.length, + updatedAt: new Date() + }); + + log('[Reviews] Updated company rating:', companyId, 'New rating:', averageRating); + } catch (error) { + console.error('[Reviews] Error updating company rating:', error.message); + } +}; + +// GET /reviews/company/:companyId - получить отзывы компании +router.get('/company/:companyId', verifyToken, async (req, res) => { + try { + const { companyId } = req.params; + + const companyReviews = await Review.find({ companyId }) + .sort({ createdAt: -1 }) + .exec(); + + log('[Reviews] Returned', companyReviews.length, 'reviews for company', companyId); + + res.json(companyReviews); + } catch (error) { + console.error('[Reviews] Error fetching reviews:', error.message); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + }); + } +}); + +// POST /reviews - создать новый отзыв +router.post('/', verifyToken, async (req, res) => { + try { + const { companyId, rating, comment } = req.body; + + if (!companyId || !rating || !comment) { + return res.status(400).json({ + error: 'Заполните все обязательные поля: компания, рейтинг и комментарий', + }); + } + + if (rating < 1 || rating > 5) { + return res.status(400).json({ + error: 'Рейтинг должен быть от 1 до 5', + }); + } + + const trimmedComment = comment.trim(); + if (trimmedComment.length < 10) { + return res.status(400).json({ + error: 'Отзыв должен содержать минимум 10 символов', + }); + } + + if (trimmedComment.length > 1000) { + return res.status(400).json({ + error: 'Отзыв не должен превышать 1000 символов', + }); + } + + // Получить данные пользователя из БД для актуальной информации + const User = require('../models/User'); + const Company = require('../models/Company'); + + const user = await User.findById(req.userId); + const userCompany = user && user.companyId ? await Company.findById(user.companyId) : null; + + if (!user) { + return res.status(404).json({ + error: 'Пользователь не найден', + }); + } + + // Создать новый отзыв + const newReview = new Review({ + companyId, + authorCompanyId: user.companyId || req.companyId, + authorName: user.firstName && user.lastName + ? `${user.firstName} ${user.lastName}` + : req.user?.firstName && req.user?.lastName + ? `${req.user.firstName} ${req.user.lastName}` + : 'Аноним', + authorCompany: userCompany?.fullName || userCompany?.shortName || req.user?.companyName || 'Компания', + rating: parseInt(rating), + comment: trimmedComment, + verified: true, + createdAt: new Date(), + updatedAt: new Date() + }); + + const savedReview = await newReview.save(); + + log('[Reviews] New review created:', savedReview._id); + + // Пересчитываем рейтинг компании + await updateCompanyRating(companyId); + + res.status(201).json(savedReview); + } catch (error) { + console.error('[Reviews] Error creating review:', error.message); + res.status(500).json({ + error: 'Ошибка при сохранении отзыва', + message: error.message, + }); + } +}); + +module.exports = router; diff --git a/server/routers/procurement/routes/search.js b/server/routers/procurement/routes/search.js new file mode 100644 index 0000000..3f064b0 --- /dev/null +++ b/server/routers/procurement/routes/search.js @@ -0,0 +1,337 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Company = require('../models/Company'); + +// Функция для логирования с проверкой DEV переменной +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +// GET /search/recommendations - получить рекомендации компаний (ДОЛЖЕН быть ПЕРЕД /*) +router.get('/recommendations', verifyToken, async (req, res) => { + try { + // Получить компанию пользователя, чтобы исключить её из результатов + const User = require('../models/User'); + const user = await User.findById(req.userId); + + let filter = {}; + if (user && user.companyId) { + filter._id = { $ne: user.companyId }; + } + + const companies = await Company.find(filter) + .sort({ rating: -1 }) + .limit(5); + + const recommendations = companies.map(company => ({ + id: company._id.toString(), + name: company.fullName || company.shortName, + industry: company.industry, + logo: company.logo, + matchScore: Math.floor(Math.random() * 30 + 70), // 70-100 + reason: 'Matches your search criteria' + })); + + log('[Search] Returned recommendations:', recommendations.length); + + res.json(recommendations); + } catch (error) { + console.error('[Search] Recommendations error:', error.message); + res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + +// GET /search - Поиск компаний +router.get('/', verifyToken, async (req, res) => { + try { + console.log('[Search] === NEW VERSION WITH FIXED SIZE FILTER ==='); + + const { + query = '', + page = 1, + limit = 10, + offset, // Добавляем поддержку offset для точной пагинации + industries, + companySize, + geography, + minRating = 0, + hasReviews, + hasAcceptedDocs, + sortBy = 'relevance', + sortOrder = 'desc', + minEmployees, // Кастомный фильтр: минимум сотрудников + maxEmployees // Кастомный фильтр: максимум сотрудников + } = req.query; + + console.log('[Search] Filters:', { minEmployees, maxEmployees, companySize }); + + // Получить компанию пользователя, чтобы исключить её из результатов + const User = require('../models/User'); + const user = await User.findById(req.userId); + + log('[Search] Request params:', { query, industries, companySize, geography, minRating, hasReviews, hasAcceptedDocs, sortBy, sortOrder }); + + // Маппинг кодов фильтров на значения в БД + const industryMap = { + 'it': 'IT', + 'finance': 'Финансы', + 'manufacturing': 'Производство', + 'construction': 'Строительство', + 'retail': 'Розничная торговля', + 'wholesale': 'Оптовая торговля', + 'logistics': 'Логистика', + 'healthcare': 'Здравоохранение', + 'education': 'Образование', + 'consulting': 'Консалтинг', + 'marketing': 'Маркетинг', + 'realestate': 'Недвижимость', + 'food': 'Пищевая промышленность', + 'agriculture': 'Сельское хозяйство', + 'energy': 'Энергетика', + 'telecom': 'Телекоммуникации', + 'media': 'Медиа' + }; + + // Начальный фильтр: исключить собственную компанию + let filters = []; + + if (user && user.companyId) { + filters.push({ _id: { $ne: user.companyId } }); + } + + // Текстовый поиск + if (query && query.trim()) { + const q = query.toLowerCase(); + filters.push({ + $or: [ + { fullName: { $regex: q, $options: 'i' } }, + { shortName: { $regex: q, $options: 'i' } }, + { slogan: { $regex: q, $options: 'i' } }, + { industry: { $regex: q, $options: 'i' } } + ] + }); + } + + // Фильтр по отраслям - преобразуем коды в значения БД + if (industries) { + const industryList = Array.isArray(industries) ? industries : [industries]; + if (industryList.length > 0) { + const dbIndustries = industryList + .map(code => industryMap[code]) + .filter(val => val !== undefined); + + log('[Search] Raw industries param:', industries); + log('[Search] Industry codes:', industryList, 'Mapped to:', dbIndustries); + + if (dbIndustries.length > 0) { + filters.push({ industry: { $in: dbIndustries } }); + log('[Search] Added industry filter:', { industry: { $in: dbIndustries } }); + } else { + log('[Search] No industries mapped! Codes were:', industryList); + } + } + } + + // Функция для парсинга диапазона из строки вида "51-250" или "500+" + const parseEmployeeRange = (sizeStr) => { + if (sizeStr.includes('+')) { + const min = parseInt(sizeStr.replace('+', '')); + return { min, max: Infinity }; + } + const parts = sizeStr.split('-'); + return { + min: parseInt(parts[0]), + max: parts[1] ? parseInt(parts[1]) : parseInt(parts[0]) + }; + }; + + // Функция для проверки пересечения двух диапазонов + const rangesOverlap = (range1, range2) => { + return range1.min <= range2.max && range1.max >= range2.min; + }; + + // Фильтр по размеру компании (чекбоксы) или кастомный диапазон + // Важно: этот фильтр должен получить все компании для корректной работы пересечения диапазонов + let sizeFilteredIds = null; + if ((companySize && companySize.length > 0) || minEmployees || maxEmployees) { + // Получаем все компании (без других фильтров, так как размер компании - это property-based фильтр) + const allCompanies = await Company.find({}); + + log('[Search] Employee size filter - checking companies:', allCompanies.length); + + let matchingIds = []; + + // Если есть кастомный диапазон - используем его + if (minEmployees || maxEmployees) { + const customRange = { + min: minEmployees ? parseInt(minEmployees, 10) : 0, + max: maxEmployees ? parseInt(maxEmployees, 10) : Infinity + }; + + log('[Search] Custom employee range filter:', customRange); + + matchingIds = allCompanies + .filter(company => { + if (!company.companySize) { + log('[Search] Company has no size:', company.fullName); + return false; + } + + const companyRange = parseEmployeeRange(company.companySize); + const overlaps = rangesOverlap(companyRange, customRange); + + log('[Search] Checking overlap:', { + company: company.fullName, + companyRange, + customRange, + overlaps + }); + + return overlaps; + }) + .map(c => c._id); + + log('[Search] Matching companies by custom range:', matchingIds.length); + } + // Иначе используем чекбоксы + else if (companySize && companySize.length > 0) { + const sizeList = Array.isArray(companySize) ? companySize : [companySize]; + + log('[Search] Company size checkboxes filter:', sizeList); + + matchingIds = allCompanies + .filter(company => { + if (!company.companySize) { + return false; + } + + const companyRange = parseEmployeeRange(company.companySize); + + // Проверяем пересечение с любым из выбранных диапазонов + const matches = sizeList.some(selectedSize => { + const filterRange = parseEmployeeRange(selectedSize); + const overlaps = rangesOverlap(companyRange, filterRange); + log('[Search] Check:', company.fullName, companyRange, 'vs', filterRange, '=', overlaps); + return overlaps; + }); + + return matches; + }) + .map(c => c._id); + + log('[Search] Matching companies by size checkboxes:', matchingIds.length); + } + + // Сохраняем ID для дальнейшей фильтрации + sizeFilteredIds = matchingIds; + log('[Search] Size filtered IDs count:', sizeFilteredIds.length); + } + + // Фильтр по географии + if (geography) { + const geoList = Array.isArray(geography) ? geography : [geography]; + if (geoList.length > 0) { + filters.push({ partnerGeography: { $in: geoList } }); + log('[Search] Geography filter:', { partnerGeography: { $in: geoList } }); + } + } + + // Фильтр по рейтингу + if (minRating) { + const rating = parseFloat(minRating); + if (rating > 0) { + filters.push({ rating: { $gte: rating } }); + } + } + + // Фильтр по отзывам + if (hasReviews === 'true') { + filters.push({ verified: true }); + } + + // Фильтр по акцептам + if (hasAcceptedDocs === 'true') { + filters.push({ verified: true }); + } + + // Применяем фильтр по размеру компании (если был задан) + if (sizeFilteredIds !== null) { + if (sizeFilteredIds.length > 0) { + filters.push({ _id: { $in: sizeFilteredIds } }); + log('[Search] Applied size filter, IDs:', sizeFilteredIds.length); + } else { + // Если нет подходящих компаний по размеру, возвращаем пустой результат + filters.push({ _id: null }); + log('[Search] No companies match size criteria'); + } + } + + // Комбинировать все фильтры + let filter = filters.length > 0 ? { $and: filters } : {}; + + // Пагинация - используем offset если передан, иначе вычисляем из page + const limitNum = parseInt(limit) || 10; + const skip = offset !== undefined ? parseInt(offset) : ((parseInt(page) || 1) - 1) * limitNum; + const pageNum = offset !== undefined ? Math.floor(skip / limitNum) + 1 : parseInt(page) || 1; + + // Сортировка + let sortOptions = {}; + if (sortBy === 'name') { + sortOptions.fullName = sortOrder === 'asc' ? 1 : -1; + } else { + sortOptions.rating = sortOrder === 'asc' ? 1 : -1; + } + + log('[Search] Final MongoDB filter:', JSON.stringify(filter, null, 2)); + + let filterDebug = filters.length > 0 ? { $and: filters } : {}; + const allCompanies = await Company.find({}); + log('[Search] All companies in DB:', allCompanies.map(c => ({ name: c.fullName, geography: c.partnerGeography, industry: c.industry }))); + + const total = await Company.countDocuments(filter); + const companies = await Company.find(filter) + .sort(sortOptions) + .skip(skip) + .limit(limitNum); + + const paginatedResults = companies.map(c => ({ + ...c.toObject(), + id: c._id + })); + + log('[Search] Query:', query, 'Industries:', industries, 'Size:', companySize, 'Geo:', geography); + log('[Search] Total found:', total, 'Returning:', paginatedResults.length, 'companies'); + log('[Search] Company details:', paginatedResults.map(c => ({ name: c.fullName, industry: c.industry }))); + + res.json({ + companies: paginatedResults, + total, + page: pageNum, + totalPages: Math.ceil(total / limitNum), + _debug: { + filter: JSON.stringify(filter), + industriesReceived: industries + } + }); + } catch (error) { + console.error('[Search] Error:', error.message); + res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + +module.exports = router; + + diff --git a/server/routers/procurement/scripts/migrate-messages.js b/server/routers/procurement/scripts/migrate-messages.js new file mode 100644 index 0000000..f77ba9b --- /dev/null +++ b/server/routers/procurement/scripts/migrate-messages.js @@ -0,0 +1,92 @@ +const mongoose = require('../../../utils/mongoose'); +const { ObjectId } = mongoose.Types; +const Message = require('../models/Message'); +require('dotenv').config(); + +async function migrateMessages() { + try { + // Подключение к MongoDB происходит через server/utils/mongoose.ts + console.log('[Migration] Checking MongoDB connection...'); + if (mongoose.connection.readyState !== 1) { + console.log('[Migration] Waiting for MongoDB connection...'); + await new Promise((resolve) => { + mongoose.connection.once('connected', resolve); + }); + } + console.log('[Migration] Connected to MongoDB'); + + // Найти все сообщения + const allMessages = await Message.find().exec(); + console.log('[Migration] Found', allMessages.length, 'total messages'); + + let fixedCount = 0; + let errorCount = 0; + + // Проходим по каждому сообщению + for (const message of allMessages) { + try { + const threadId = message.threadId; + if (!threadId) { + console.log('[Migration] Skipping message', message._id, '- no threadId'); + continue; + } + + // Парсим threadId формата "thread-id1-id2" или "id1-id2" + let ids = threadId.replace('thread-', '').split('-'); + + if (ids.length < 2) { + console.log('[Migration] Invalid threadId format:', threadId); + errorCount++; + continue; + } + + const companyId1 = ids[0]; + const companyId2 = ids[1]; + + // Сравниваем с senderCompanyId + const senderIdString = message.senderCompanyId.toString ? message.senderCompanyId.toString() : message.senderCompanyId; + const expectedRecipient = senderIdString === companyId1 ? companyId2 : companyId1; + + // Если recipientCompanyId не установлена или неправильная - исправляем + if (!message.recipientCompanyId || message.recipientCompanyId.toString() !== expectedRecipient) { + console.log('[Migration] Fixing message', message._id); + console.log(' Old recipientCompanyId:', message.recipientCompanyId); + console.log(' Expected:', expectedRecipient); + + // Конвертируем в ObjectId если нужно + let recipientObjectId = expectedRecipient; + try { + if (typeof expectedRecipient === 'string' && ObjectId.isValid(expectedRecipient)) { + recipientObjectId = new ObjectId(expectedRecipient); + } + } catch (e) { + console.log(' Could not convert to ObjectId'); + } + + await Message.updateOne( + { _id: message._id }, + { recipientCompanyId: recipientObjectId } + ); + + fixedCount++; + console.log(' ✅ Fixed'); + } + } catch (err) { + console.error('[Migration] Error processing message', message._id, ':', err.message); + errorCount++; + } + } + + console.log('[Migration] ✅ Migration completed!'); + console.log('[Migration] Fixed:', fixedCount, 'messages'); + console.log('[Migration] Errors:', errorCount); + + await mongoose.connection.close(); + console.log('[Migration] Disconnected from MongoDB'); + } catch (err) { + console.error('[Migration] ❌ Error:', err.message); + process.exit(1); + } +} + +migrateMessages(); diff --git a/server/routers/procurement/scripts/recreate-test-user.js b/server/routers/procurement/scripts/recreate-test-user.js new file mode 100644 index 0000000..af2c530 --- /dev/null +++ b/server/routers/procurement/scripts/recreate-test-user.js @@ -0,0 +1,382 @@ +const mongoose = require('../../../utils/mongoose'); +require('dotenv').config(); + +// Импорт моделей +const User = require('../models/User'); +const Company = require('../models/Company'); +const Request = require('../models/Request'); + +// Подключение к MongoDB происходит через server/utils/mongoose.ts +// Проверяем, подключено ли уже +const ensureConnection = async () => { + if (mongoose.connection.readyState === 1) { + console.log('✅ MongoDB уже подключено'); + return; + } + + console.log('⏳ Ожидание подключения к MongoDB...'); + await new Promise((resolve) => { + if (mongoose.connection.readyState === 1) { + resolve(); + } else { + mongoose.connection.once('connected', resolve); + } + }); + console.log('✅ Подключено к MongoDB'); +}; + +const recreateTestUser = async () => { + try { + await ensureConnection(); + + const presetCompanyId = new mongoose.Types.ObjectId('68fe2ccda3526c303ca06796'); + const presetUserEmail = 'admin@test-company.ru'; + + const presetCompanyId2 = new mongoose.Types.ObjectId('68fe2ccda3526c303ca06797'); + const presetUserEmail2 = 'manager@partner-company.ru'; + + // Удалить старых тестовых пользователей + console.log('🗑️ Удаление старых тестовых пользователей...'); + const testEmails = [presetUserEmail, presetUserEmail2]; + + for (const email of testEmails) { + const oldUser = await User.findOne({ email }); + if (oldUser) { + // Удалить связанную компанию + if (oldUser.companyId) { + await Company.findByIdAndDelete(oldUser.companyId); + console.log(` ✓ Старая компания для ${email} удалена`); + } + await User.findByIdAndDelete(oldUser._id); + console.log(` ✓ Старый пользователь ${email} удален`); + } else { + console.log(` ℹ️ Пользователь ${email} не найден`); + } + } + + // Создать новую компанию с правильной кодировкой UTF-8 + console.log('\n🏢 Создание тестовой компании...'); + const company = await Company.create({ + _id: presetCompanyId, + fullName: 'ООО "Тестовая Компания"', + shortName: 'Тестовая Компания', + inn: '1234567890', + ogrn: '1234567890123', + legalForm: 'ООО', + industry: 'IT', + companySize: '51-250', + website: 'https://test-company.ru', + phone: '+7 (999) 123-45-67', + email: 'info@test-company.ru', + description: 'Тестовая компания для разработки', + legalAddress: 'г. Москва, ул. Тестовая, д. 1', + actualAddress: 'г. Москва, ул. Тестовая, д. 1', + foundedYear: 2015, + employeeCount: '51-250', + revenue: 'До 120 млн ₽', + rating: 4.5, + reviews: 10, + verified: true, + partnerGeography: ['moscow', 'russia_all'], + slogan: 'Ваш надежный партнер в IT', + }); + console.log(' ✓ Компания создана:', company.fullName); + + // Создать первого пользователя с правильной кодировкой UTF-8 + console.log('\n👤 Создание первого тестового пользователя...'); + const user = await User.create({ + email: presetUserEmail, + password: 'SecurePass123!', + firstName: 'Иван', + lastName: 'Иванов', + position: 'Директор', + phone: '+7 (999) 123-45-67', + companyId: company._id, + }); + console.log(' ✓ Пользователь создан:', user.firstName, user.lastName); + + // Создать вторую компанию + console.log('\n🏢 Создание второй тестовой компании...'); + const company2 = await Company.create({ + _id: presetCompanyId2, + fullName: 'ООО "Партнер"', + shortName: 'Партнер', + inn: '9876543210', + ogrn: '1089876543210', + legalForm: 'ООО', + industry: 'Торговля', + companySize: '11-50', + website: 'https://partner-company.ru', + phone: '+7 (495) 987-65-43', + email: 'info@partner-company.ru', + description: 'Надежный партнер для бизнеса', + legalAddress: 'г. Санкт-Петербург, пр. Невский, д. 100', + actualAddress: 'г. Санкт-Петербург, пр. Невский, д. 100', + foundedYear: 2018, + employeeCount: '11-50', + revenue: 'До 60 млн ₽', + rating: 4.3, + reviews: 5, + verified: true, + partnerGeography: ['spb', 'russia_all'], + slogan: 'Качество и надежность', + }); + console.log(' ✓ Компания создана:', company2.fullName); + + // Создать второго пользователя + console.log('\n👤 Создание второго тестового пользователя...'); + const user2 = await User.create({ + email: presetUserEmail2, + password: 'SecurePass123!', + firstName: 'Петр', + lastName: 'Петров', + position: 'Менеджер', + phone: '+7 (495) 987-65-43', + companyId: company2._id, + }); + console.log(' ✓ Пользователь создан:', user2.firstName, user2.lastName); + + // Проверка что данные сохранены правильно + console.log('\n✅ Проверка данных:'); + console.log('\n Пользователь 1:'); + console.log(' Email:', user.email); + console.log(' Имя:', user.firstName); + console.log(' Фамилия:', user.lastName); + console.log(' Компания:', company.fullName); + console.log(' Должность:', user.position); + + console.log('\n Пользователь 2:'); + console.log(' Email:', user2.email); + console.log(' Имя:', user2.firstName); + console.log(' Фамилия:', user2.lastName); + console.log(' Компания:', company2.fullName); + console.log(' Должность:', user2.position); + + console.log('\n✅ ГОТОВО! Тестовые пользователи созданы с правильной кодировкой UTF-8'); + console.log('\n📋 Данные для входа:'); + console.log('\n Пользователь 1:'); + console.log(' Email: admin@test-company.ru'); + console.log(' Пароль: SecurePass123!'); + console.log('\n Пользователь 2:'); + console.log(' Email: manager@partner-company.ru'); + console.log(' Пароль: SecurePass123!'); + console.log(''); + + // Создать дополнительные тестовые компании для поиска + console.log('\n🏢 Создание дополнительных тестовых компаний...'); + const testCompanies = [ + { + fullName: 'ООО "ТехноСтрой"', + shortName: 'ТехноСтрой', + inn: '7707083894', + ogrn: '1077707083894', + legalForm: 'ООО', + industry: 'Строительство', + companySize: '51-250', + website: 'https://technostroy.ru', + phone: '+7 (495) 111-22-33', + email: 'info@technostroy.ru', + description: 'Строительство промышленных объектов', + foundedYear: 2010, + employeeCount: '51-250', + revenue: 'До 2 млрд ₽', + rating: 4.2, + reviews: 15, + verified: true, + partnerGeography: ['moscow', 'russia_all'], + slogan: 'Строим будущее вместе', + }, + { + fullName: 'АО "ФинансГрупп"', + shortName: 'ФинансГрупп', + inn: '7707083895', + ogrn: '1077707083895', + legalForm: 'АО', + industry: 'Финансы', + companySize: '500+', + website: 'https://finansgrupp.ru', + phone: '+7 (495) 222-33-44', + email: 'contact@finansgrupp.ru', + description: 'Финансовые услуги для бизнеса', + foundedYear: 2005, + employeeCount: '500+', + revenue: 'Более 2 млрд ₽', + rating: 4.8, + reviews: 50, + verified: true, + partnerGeography: ['moscow', 'russia_all', 'international'], + slogan: 'Финансовая стабильность', + }, + { + fullName: 'ООО "ИТ Решения"', + shortName: 'ИТ Решения', + inn: '7707083896', + ogrn: '1077707083896', + legalForm: 'ООО', + industry: 'IT', + companySize: '11-50', + website: 'https://it-solutions.ru', + phone: '+7 (495) 333-44-55', + email: 'hello@it-solutions.ru', + description: 'Разработка программного обеспечения', + foundedYear: 2018, + employeeCount: '11-50', + revenue: 'До 60 млн ₽', + rating: 4.5, + reviews: 8, + verified: true, + partnerGeography: ['moscow', 'spb', 'russia_all'], + slogan: 'Инновации для вашего бизнеса', + }, + { + fullName: 'ООО "ЛогистикПро"', + shortName: 'ЛогистикПро', + inn: '7707083897', + ogrn: '1077707083897', + legalForm: 'ООО', + industry: 'Логистика', + companySize: '51-250', + website: 'https://logistikpro.ru', + phone: '+7 (495) 444-55-66', + email: 'info@logistikpro.ru', + description: 'Транспортные и логистические услуги', + foundedYear: 2012, + employeeCount: '51-250', + revenue: 'До 120 млн ₽', + rating: 4.3, + reviews: 20, + verified: true, + partnerGeography: ['russia_all', 'cis'], + slogan: 'Доставим в срок', + }, + { + fullName: 'ООО "ПродуктТрейд"', + shortName: 'ПродуктТрейд', + inn: '7707083898', + ogrn: '1077707083898', + legalForm: 'ООО', + industry: 'Оптовая торговля', + companySize: '251-500', + website: 'https://produkttrade.ru', + phone: '+7 (495) 555-66-77', + email: 'sales@produkttrade.ru', + description: 'Оптовая торговля продуктами питания', + foundedYear: 2008, + employeeCount: '251-500', + revenue: 'До 2 млрд ₽', + rating: 4.1, + reviews: 30, + verified: true, + partnerGeography: ['moscow', 'russia_all'], + slogan: 'Качество и надежность', + }, + { + fullName: 'ООО "МедСервис"', + shortName: 'МедСервис', + inn: '7707083899', + ogrn: '1077707083899', + legalForm: 'ООО', + industry: 'Здравоохранение', + companySize: '11-50', + website: 'https://medservice.ru', + phone: '+7 (495) 666-77-88', + email: 'info@medservice.ru', + description: 'Медицинские услуги и оборудование', + foundedYear: 2016, + employeeCount: '11-50', + revenue: 'До 60 млн ₽', + rating: 4.6, + reviews: 12, + verified: true, + partnerGeography: ['moscow', 'central'], + slogan: 'Забота о вашем здоровье', + }, + ]; + + for (const companyData of testCompanies) { + await Company.updateOne( + { inn: companyData.inn }, + { $set: companyData }, + { upsert: true } + ); + console.log(` ✓ Компания создана/обновлена: ${companyData.shortName}`); + } + + // Создать тестовые запросы + console.log('\n📨 Создание тестовых запросов...'); + await Request.deleteMany({}); + + const companies = await Company.find().limit(10).exec(); + const testCompanyId = company._id.toString(); + const requests = []; + const now = new Date(); + + // Создаем отправленные запросы (от тестовой компании) + for (let i = 0; i < 5; i++) { + const recipientCompany = companies[i % companies.length]; + if (recipientCompany._id.toString() === testCompanyId) { + continue; + } + + const createdAt = new Date(now.getTime() - i * 24 * 60 * 60 * 1000); + + requests.push({ + senderCompanyId: testCompanyId, + recipientCompanyId: recipientCompany._id.toString(), + subject: `Запрос на поставку ${i + 1}`, + text: `Здравствуйте! Интересует поставка товаров/услуг. Запрос ${i + 1}. Прошу предоставить коммерческое предложение.`, + files: [], + responseFiles: [], + status: i % 3 === 0 ? 'accepted' : i % 3 === 1 ? 'rejected' : 'pending', + response: i % 3 === 0 + ? 'Благодарим за запрос! Готовы предоставить услуги. Отправили КП на почту.' + : i % 3 === 1 + ? 'К сожалению, в данный момент не можем предоставить эти услуги.' + : null, + respondedAt: i % 3 !== 2 ? new Date(createdAt.getTime() + 2 * 60 * 60 * 1000) : null, + createdAt, + updatedAt: i % 3 !== 2 ? new Date(createdAt.getTime() + 2 * 60 * 60 * 1000) : createdAt, + }); + } + + // Создаем полученные запросы (к тестовой компании) + for (let i = 0; i < 3; i++) { + const senderCompany = companies[(i + 2) % companies.length]; + if (senderCompany._id.toString() === testCompanyId) { + continue; + } + + const createdAt = new Date(now.getTime() - (i + 1) * 12 * 60 * 60 * 1000); + + requests.push({ + senderCompanyId: senderCompany._id.toString(), + recipientCompanyId: testCompanyId, + subject: `Предложение о сотрудничестве ${i + 1}`, + text: `Добрый день! Предлагаем сотрудничество. Запрос ${i + 1}. Заинтересованы в вашей продукции.`, + files: [], + responseFiles: [], + status: 'pending', + response: null, + respondedAt: null, + createdAt, + updatedAt: createdAt, + }); + } + + if (requests.length > 0) { + await Request.insertMany(requests); + console.log(` ✓ Создано ${requests.length} тестовых запросов`); + } + + await mongoose.connection.close(); + process.exit(0); + } catch (error) { + console.error('\n❌ Ошибка:', error.message); + console.error(error); + process.exit(1); + } +}; + +// Запуск +recreateTestUser(); + diff --git a/server/routers/procurement/scripts/seed-activities.js b/server/routers/procurement/scripts/seed-activities.js new file mode 100644 index 0000000..7e6e949 --- /dev/null +++ b/server/routers/procurement/scripts/seed-activities.js @@ -0,0 +1,126 @@ +const mongoose = require('../../../utils/mongoose'); +require('dotenv').config(); + +// Подключение моделей +const Activity = require('../models/Activity'); +const User = require('../models/User'); +const Company = require('../models/Company'); + +const activityTemplates = [ + { + type: 'request_received', + title: 'Получен новый запрос', + description: 'Компания отправила вам запрос на поставку товаров', + }, + { + type: 'request_sent', + title: 'Запрос отправлен', + description: 'Ваш запрос был отправлен компании', + }, + { + type: 'request_response', + title: 'Получен ответ на запрос', + description: 'Компания ответила на ваш запрос', + }, + { + type: 'product_accepted', + title: 'Товар акцептован', + description: 'Ваш товар был акцептован компанией', + }, + { + type: 'message_received', + title: 'Новое сообщение', + description: 'Вы получили новое сообщение от компании', + }, + { + type: 'review_received', + title: 'Новый отзыв', + description: 'Компания оставила отзыв о сотрудничестве', + }, + { + type: 'profile_updated', + title: 'Профиль обновлен', + description: 'Информация о вашей компании была обновлена', + }, + { + type: 'buy_product_added', + title: 'Добавлен товар для закупки', + description: 'В раздел "Я покупаю" добавлен новый товар', + }, +]; + +async function seedActivities() { + try { + // Подключение к MongoDB происходит через server/utils/mongoose.ts + console.log('🌱 Checking MongoDB connection...'); + if (mongoose.connection.readyState !== 1) { + console.log('⏳ Waiting for MongoDB connection...'); + await new Promise((resolve) => { + mongoose.connection.once('connected', resolve); + }); + } + console.log('✅ Connected to MongoDB'); + + // Найти тестового пользователя + const testUser = await User.findOne({ email: 'admin@test-company.ru' }); + if (!testUser) { + console.log('❌ Test user not found. Please run recreate-test-user.js first.'); + process.exit(1); + } + + const company = await Company.findById(testUser.companyId); + if (!company) { + console.log('❌ Company not found'); + process.exit(1); + } + + // Найти другие компании для связанных активностей + const otherCompanies = await Company.find({ + _id: { $ne: company._id } + }).limit(3); + + console.log('🗑️ Clearing existing activities...'); + await Activity.deleteMany({ companyId: company._id.toString() }); + + console.log('➕ Creating activities...'); + const activities = []; + + for (let i = 0; i < 8; i++) { + const template = activityTemplates[i % activityTemplates.length]; + const relatedCompany = otherCompanies[i % otherCompanies.length]; + + const activity = { + companyId: company._id.toString(), + userId: testUser._id.toString(), + type: template.type, + title: template.title, + description: template.description, + relatedCompanyId: relatedCompany?._id.toString(), + relatedCompanyName: relatedCompany?.shortName || relatedCompany?.fullName, + read: i >= 5, // Первые 5 непрочитанные + createdAt: new Date(Date.now() - i * 3600000), // Каждый час назад + }; + + activities.push(activity); + } + + await Activity.insertMany(activities); + + console.log(`✅ Created ${activities.length} activities`); + console.log('✨ Activities seeded successfully!'); + + await mongoose.connection.close(); + console.log('👋 Database connection closed'); + } catch (error) { + console.error('❌ Error seeding activities:', error); + process.exit(1); + } +} + +// Запуск +if (require.main === module) { + seedActivities(); +} + +module.exports = { seedActivities }; + diff --git a/server/routers/procurement/scripts/seed-requests.js b/server/routers/procurement/scripts/seed-requests.js new file mode 100644 index 0000000..59b4883 --- /dev/null +++ b/server/routers/procurement/scripts/seed-requests.js @@ -0,0 +1,118 @@ +const mongoose = require('../../../utils/mongoose'); +const Request = require('../models/Request'); +const Company = require('../models/Company'); +const User = require('../models/User'); + +async function seedRequests() { + try { + // Подключение к MongoDB происходит через server/utils/mongoose.ts + if (mongoose.connection.readyState !== 1) { + console.log('⏳ Waiting for MongoDB connection...'); + await new Promise((resolve) => { + mongoose.connection.once('connected', resolve); + }); + } + console.log('✅ Connected to MongoDB'); + + // Получаем все компании + const companies = await Company.find().limit(10).exec(); + if (companies.length < 2) { + console.error('❌ Need at least 2 companies in database'); + process.exit(1); + } + + // Получаем тестового пользователя + const testUser = await User.findOne({ email: 'admin@test-company.ru' }).exec(); + if (!testUser) { + console.error('❌ Test user not found'); + process.exit(1); + } + + const testCompanyId = testUser.companyId.toString(); + console.log('📋 Test company ID:', testCompanyId); + console.log('📋 Found', companies.length, 'companies'); + + // Удаляем старые запросы + await Request.deleteMany({}); + console.log('🗑️ Cleared old requests'); + + const requests = []; + const now = new Date(); + + // Создаем отправленные запросы (от тестовой компании) + for (let i = 0; i < 5; i++) { + const recipientCompany = companies[i % companies.length]; + if (recipientCompany._id.toString() === testCompanyId) { + continue; + } + + const createdAt = new Date(now.getTime() - i * 24 * 60 * 60 * 1000); // За последние 5 дней + + requests.push({ + senderCompanyId: testCompanyId, + recipientCompanyId: recipientCompany._id.toString(), + subject: `Запрос на поставку ${i + 1}`, + text: `Здравствуйте! Интересует поставка товаров/услуг. Запрос ${i + 1}. Прошу предоставить коммерческое предложение.`, + files: [], + responseFiles: [], + status: i % 3 === 0 ? 'accepted' : i % 3 === 1 ? 'rejected' : 'pending', + response: i % 3 === 0 + ? 'Благодарим за запрос! Готовы предоставить услуги. Отправили КП на почту.' + : i % 3 === 1 + ? 'К сожалению, в данный момент не можем предоставить эти услуги.' + : null, + respondedAt: i % 3 !== 2 ? new Date(createdAt.getTime() + 2 * 60 * 60 * 1000) : null, + createdAt, + updatedAt: i % 3 !== 2 ? new Date(createdAt.getTime() + 2 * 60 * 60 * 1000) : createdAt, + }); + } + + // Создаем полученные запросы (к тестовой компании) + for (let i = 0; i < 3; i++) { + const senderCompany = companies[(i + 2) % companies.length]; + if (senderCompany._id.toString() === testCompanyId) { + continue; + } + + const createdAt = new Date(now.getTime() - (i + 1) * 12 * 60 * 60 * 1000); // За последние 1.5 дня + + requests.push({ + senderCompanyId: senderCompany._id.toString(), + recipientCompanyId: testCompanyId, + subject: `Предложение о сотрудничестве ${i + 1}`, + text: `Добрый день! Предлагаем сотрудничество. Запрос ${i + 1}. Заинтересованы в вашей продукции.`, + files: [], + responseFiles: [], + status: 'pending', + response: null, + respondedAt: null, + createdAt, + updatedAt: createdAt, + }); + } + + // Сохраняем все запросы + const savedRequests = await Request.insertMany(requests); + console.log('✅ Created', savedRequests.length, 'test requests'); + + // Статистика + const sentCount = await Request.countDocuments({ senderCompanyId: testCompanyId }); + const receivedCount = await Request.countDocuments({ recipientCompanyId: testCompanyId }); + const withResponses = await Request.countDocuments({ senderCompanyId: testCompanyId, response: { $ne: null } }); + + console.log('📊 Statistics:'); + console.log(' - Sent requests:', sentCount); + console.log(' - Received requests:', receivedCount); + console.log(' - With responses:', withResponses); + + } catch (error) { + console.error('❌ Error:', error); + process.exit(1); + } finally { + await mongoose.connection.close(); + console.log('👋 Disconnected from MongoDB'); + } +} + +seedRequests(); + diff --git a/server/routers/procurement/scripts/test-logging.js b/server/routers/procurement/scripts/test-logging.js new file mode 100644 index 0000000..e914f23 --- /dev/null +++ b/server/routers/procurement/scripts/test-logging.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +/** + * Скрипт для тестирования логирования + * + * Использование: + * node stubs/scripts/test-logging.js # Логи скрыты (DEV не установлена) + * DEV=true node stubs/scripts/test-logging.js # Логи видны + */ + +// Функция логирования из маршрутов +const log = (message, data = '') => { + if (process.env.DEV === 'true') { + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } +}; + +console.log(''); +console.log('='.repeat(60)); +console.log('TEST: Логирование с переменной окружения DEV'); +console.log('='.repeat(60)); +console.log(''); + +console.log('Значение DEV:', process.env.DEV || '(не установлена)'); +console.log(''); + +// Тестируем различные логи +log('[Auth] Token verified - userId: 68fe2ccda3526c303ca06799 companyId: 68fe2ccda3526c303ca06796'); +log('[Auth] Generating token for userId:', '68fe2ccda3526c303ca06799'); +log('[BuyProducts] Found', 0, 'products for company 68fe2ccda3526c303ca06796'); +log('[Products] GET Fetching products for companyId:', '68fe2ccda3526c303ca06799'); +log('[Products] Found', 1, 'products'); +log('[Reviews] Returned', 0, 'reviews for company 68fe2ccda3526c303ca06796'); +log('[Messages] Fetching threads for companyId:', '68fe2ccda3526c303ca06796'); +log('[Messages] Found', 4, 'messages for company'); +log('[Messages] Returned', 3, 'unique threads'); +log('[Search] Request params:', { query: '', page: 1 }); + +console.log(''); +console.log('='.repeat(60)); +console.log('РЕЗУЛЬТАТ:'); +console.log('='.repeat(60)); + +if (process.env.DEV === 'true') { + console.log('✅ DEV=true - логи ВИДНЫ выше'); +} else { + console.log('❌ DEV не установлена или != "true" - логи СКРЫТЫ'); + console.log(''); + console.log('Для включения логов запустите:'); + console.log(' export DEV=true && npm start (Linux/Mac)'); + console.log(' $env:DEV = "true"; npm start (PowerShell)'); + console.log(' set DEV=true && npm start (CMD)'); +} + +console.log(''); +console.log('='.repeat(60)); +console.log(''); diff --git a/server/routers/questioneer/public/static/js/common.js b/server/routers/questioneer/public/static/js/common.js index 7007de8..9a6a943 100644 --- a/server/routers/questioneer/public/static/js/common.js +++ b/server/routers/questioneer/public/static/js/common.js @@ -187,6 +187,7 @@ function showConfirm(message, callback, title) { function generateQRCode(data, size) { const typeNumber = 0; // Автоматическое определение const errorCorrectionLevel = 'L'; // Низкий уровень коррекции ошибок + // eslint-disable-next-line no-undef const qr = qrcode(typeNumber, errorCorrectionLevel); qr.addData(data); qr.make(); diff --git a/server/routers/questioneer/public/static/js/create.js b/server/routers/questioneer/public/static/js/create.js index e979449..6bb97a0 100644 --- a/server/routers/questioneer/public/static/js/create.js +++ b/server/routers/questioneer/public/static/js/create.js @@ -344,21 +344,21 @@ $(document).ready(function() { // Инициализируем атрибуты required updateRequiredAttributes(); -}); - -// Обработчик удаления вопроса -$(document).on('click', '.remove-question', function() { - $(this).closest('.question-item').remove(); - updateQuestionNumbers(); - // Вызываем функцию обновления атрибутов required - updateRequiredAttributes(); -}); - -// Обработчик удаления опции -$(document).on('click', '.remove-option', function() { - $(this).closest('.option-item').remove(); + // Обработчик удаления вопроса + $(document).on('click', '.remove-question', function() { + $(this).closest('.question-item').remove(); + updateQuestionNumbers(); + + // Вызываем функцию обновления атрибутов required + updateRequiredAttributes(); + }); - // Вызываем функцию обновления атрибутов required - updateRequiredAttributes(); + // Обработчик удаления опции + $(document).on('click', '.remove-option', function() { + $(this).closest('.option-item').remove(); + + // Вызываем функцию обновления атрибутов required + updateRequiredAttributes(); + }); }); \ No newline at end of file diff --git a/server/routers/smoke-tracker/API.md b/server/routers/smoke-tracker/API.md new file mode 100644 index 0000000..553c2ec --- /dev/null +++ b/server/routers/smoke-tracker/API.md @@ -0,0 +1,833 @@ +# Smoke Tracker API — Документация для Frontend + +## Базовый URL + +``` +http://localhost:8044/smoke-tracker +``` + +В production окружении замените на соответствующий домен. + +--- + +## Оглавление + +1. [Авторизация](#авторизация) + - [Регистрация](#post-authsignup) + - [Вход](#post-authsignin) +2. [Логирование сигарет](#логирование-сигарет) + - [Записать сигарету](#post-cigarettes) + - [Получить список сигарет](#get-cigarettes) +3. [Статистика](#статистика) + - [Дневная статистика](#get-statsdaily) + - [Сводная статистика](#get-statssummary) + +--- + +## Авторизация + +Все эндпоинты, кроме `/auth/signup` и `/auth/signin`, требуют JWT-токен в заголовке: + +``` +Authorization: Bearer +``` + +Токен возвращается при успешном входе (`/auth/signin`) и действителен **12 часов**. + +--- + +### `POST /auth/signup` + +**Описание**: Регистрация нового пользователя + +**Требуется авторизация**: ❌ Нет + +**Тело запроса** (JSON): + +```json +{ + "login": "string", // обязательно, уникальный логин + "password": "string" // обязательно +} +``` + +**Пример запроса**: + +```bash +curl -X POST http://localhost:8044/smoke-tracker/auth/signup \ + -H "Content-Type: application/json" \ + -d '{ + "login": "user123", + "password": "mySecurePassword" + }' +``` + +**Ответ при успехе** (200 OK): + +```json +{ + "success": true, + "body": { + "ok": true + } +} +``` + +**Возможные ошибки**: + +- **400 Bad Request**: `"Не все поля заполнены: login, password"` — не указаны обязательные поля +- **500 Internal Server Error**: `"Пользователь с таким логином уже существует"` — логин занят + +--- + +### `POST /auth/signin` + +**Описание**: Вход в систему (получение JWT-токена) + +**Требуется авторизация**: ❌ Нет + +**Тело запроса** (JSON): + +```json +{ + "login": "string", // обязательно + "password": "string" // обязательно +} +``` + +**Пример запроса**: + +```bash +curl -X POST http://localhost:8044/smoke-tracker/auth/signin \ + -H "Content-Type: application/json" \ + -d '{ + "login": "user123", + "password": "mySecurePassword" + }' +``` + +**Ответ при успехе** (200 OK): + +```json +{ + "success": true, + "body": { + "user": { + "id": "507f1f77bcf86cd799439011", + "login": "user123", + "created": "2024-01-15T10:30:00.000Z" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } +} +``` + +**Поля ответа**: + +- `user.id` — уникальный идентификатор пользователя +- `user.login` — логин пользователя +- `user.created` — дата создания аккаунта (ISO 8601) +- `token` — JWT-токен для авторизации (без ограничений по времени действия) + +**Возможные ошибки**: + +- **400 Bad Request**: `"Не все поля заполнены: login, password"` — не указаны обязательные поля +- **500 Internal Server Error**: `"Неверный логин или пароль"` — неправильные учётные данные + +**Использование токена**: + +Сохраните токен в localStorage/sessionStorage/cookie и передавайте в заголовке всех последующих запросов: + +```javascript +// Пример для fetch API +const token = localStorage.getItem('smokeToken'); + +fetch('http://localhost:8044/smoke-tracker/cigarettes', { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } +}); +``` + +--- + +## Логирование сигарет + +### `POST /cigarettes` + +**Описание**: Записать факт выкуренной сигареты + +**Требуется авторизация**: ✅ Да (Bearer token) + +**Тело запроса** (JSON): + +```json +{ + "smokedAt": "string (ISO 8601)", // необязательно, по умолчанию — текущее время + "note": "string" // необязательно, заметка/комментарий +} +``` + +**Пример запроса**: + +```bash +curl -X POST http://localhost:8044/smoke-tracker/cigarettes \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ + -H "Content-Type: application/json" \ + -d '{ + "smokedAt": "2024-01-15T14:30:00.000Z", + "note": "После обеда" + }' +``` + +**Пример без указания времени** (будет текущее время): + +```bash +curl -X POST http://localhost:8044/smoke-tracker/cigarettes \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +**Ответ при успехе** (200 OK): + +```json +{ + "success": true, + "body": { + "id": "507f1f77bcf86cd799439012", + "userId": "507f1f77bcf86cd799439011", + "smokedAt": "2024-01-15T14:30:00.000Z", + "note": "После обеда", + "created": "2024-01-15T14:30:05.123Z" + } +} +``` + +**Поля ответа**: + +- `id` — уникальный идентификатор записи +- `userId` — ID пользователя +- `smokedAt` — дата и время курения (ISO 8601) +- `note` — заметка (если была указана) +- `created` — дата создания записи в БД + +**Возможные ошибки**: + +- **401 Unauthorized**: `"Требуется авторизация"` — не передан токен +- **401 Unauthorized**: `"Неверный или истекший токен авторизации"` — токен невалидный/просрочен +- **400 Bad Request**: `"Некорректный формат даты smokedAt"` — неверный формат даты + +--- + +### `GET /cigarettes` + +**Описание**: Получить список всех выкуренных сигарет текущего пользователя + +**Требуется авторизация**: ✅ Да (Bearer token) + +**Query-параметры** (все необязательные): + +| Параметр | Тип | Описание | Пример | +|----------|-----|----------|--------| +| `from` | string (ISO 8601) | Начало периода (включительно) | `2024-01-01T00:00:00.000Z` | +| `to` | string (ISO 8601) | Конец периода (включительно) | `2024-01-31T23:59:59.999Z` | + +**Пример запроса** (все сигареты): + +```bash +curl -X GET http://localhost:8044/smoke-tracker/cigarettes \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + +**Пример запроса** (с фильтрацией по датам): + +```bash +curl -X GET "http://localhost:8044/smoke-tracker/cigarettes?from=2024-01-01T00:00:00.000Z&to=2024-01-31T23:59:59.999Z" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + +**Ответ при успехе** (200 OK): + +```json +{ + "success": true, + "body": [ + { + "id": "507f1f77bcf86cd799439012", + "userId": "507f1f77bcf86cd799439011", + "smokedAt": "2024-01-15T10:30:00.000Z", + "note": "Утренняя", + "created": "2024-01-15T10:30:05.123Z" + }, + { + "id": "507f1f77bcf86cd799439013", + "userId": "507f1f77bcf86cd799439011", + "smokedAt": "2024-01-15T14:30:00.000Z", + "note": "После обеда", + "created": "2024-01-15T14:30:05.456Z" + } + ] +} +``` + +**Особенности**: + +- Записи отсортированы по `smokedAt` (от старых к новым) +- Если указаны `from` и/или `to`, будет применена фильтрация +- Пустой массив возвращается, если сигарет в периоде нет + +**Возможные ошибки**: + +- **401 Unauthorized**: `"Требуется авторизация"` — не передан токен +- **401 Unauthorized**: `"Неверный или истекший токен авторизации"` — токен невалидный/просрочен + +--- + +## Статистика + +### `GET /stats/daily` + +**Описание**: Получить дневную статистику по количеству сигарет для построения графика + +**Требуется авторизация**: ✅ Да (Bearer token) + +**Query-параметры** (все необязательные): + +| Параметр | Тип | Описание | Пример | По умолчанию | +|----------|-----|----------|--------|--------------| +| `from` | string (ISO 8601) | Начало периода | `2024-01-01T00:00:00.000Z` | 30 дней назад от текущей даты | +| `to` | string (ISO 8601) | Конец периода | `2024-01-31T23:59:59.999Z` | Текущая дата и время | + +**Пример запроса** (последние 30 дней): + +```bash +curl -X GET http://localhost:8044/smoke-tracker/stats/daily \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + +**Пример запроса** (с указанием периода): + +```bash +curl -X GET "http://localhost:8044/smoke-tracker/stats/daily?from=2024-01-01T00:00:00.000Z&to=2024-01-31T23:59:59.999Z" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + +**Ответ при успехе** (200 OK): + +```json +{ + "success": true, + "body": [ + { + "date": "2024-01-15", + "count": 8 + }, + { + "date": "2024-01-16", + "count": 12 + }, + { + "date": "2024-01-17", + "count": 5 + } + ] +} +``` + +**Поля ответа**: + +- `date` — дата в формате `YYYY-MM-DD` +- `count` — количество сигарет, выкуренных в этот день + +**Особенности**: + +- Данные отсортированы по дате (от старых к новым) +- Дни без сигарет **не включаются** в ответ (фронтенду нужно самостоятельно заполнить пропуски нулями при построении графика) +- Агрегация происходит по дате из поля `smokedAt` (не `created`) + +**Пример использования для графика** (Chart.js): + +```javascript +const response = await fetch('http://localhost:8044/smoke-tracker/stats/daily', { + headers: { + 'Authorization': `Bearer ${token}` + } +}); + +const { body } = await response.json(); + +// Заполнение пропущенных дней нулями +const fillMissingDates = (data, from, to) => { + const result = []; + const current = new Date(from); + const end = new Date(to); + + while (current <= end) { + const dateStr = current.toISOString().split('T')[0]; + const existing = data.find(d => d.date === dateStr); + + result.push({ + date: dateStr, + count: existing ? existing.count : 0 + }); + + current.setDate(current.getDate() + 1); + } + + return result; +}; + +const filledData = fillMissingDates(body, '2024-01-01', '2024-01-31'); + +// Данные для графика +const chartData = { + labels: filledData.map(d => d.date), + datasets: [{ + label: 'Количество сигарет', + data: filledData.map(d => d.count), + borderColor: 'rgb(255, 99, 132)', + backgroundColor: 'rgba(255, 99, 132, 0.2)', + }] +}; +``` + +**Возможные ошибки**: + +- **401 Unauthorized**: `"Требуется авторизация"` — не передан токен +- **401 Unauthorized**: `"Неверный или истекший токен авторизации"` — токен невалидный/просрочен + +--- + +### `GET /stats/summary` + +**Описание**: Получить расширенную статистику для текущего пользователя и общую по всем пользователям + +**Требуется авторизация**: ✅ Да (Bearer token) + +**Query-параметры** (все необязательные): + +| Параметр | Тип | Описание | Пример | По умолчанию | +|----------|-----|----------|--------|--------------| +| `from` | string (ISO 8601) | Начало периода | `2024-01-01T00:00:00.000Z` | 30 дней назад от текущей даты | +| `to` | string (ISO 8601) | Конец периода | `2024-01-31T23:59:59.999Z` | Текущая дата и время | + +**Пример запроса**: + +```bash +curl -X GET http://localhost:8044/smoke-tracker/stats/summary \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + +**Ответ при успехе** (200 OK): + +```json +{ + "success": true, + "body": { + "user": { + "daily": [ + { + "date": "2024-01-15", + "count": 8 + }, + { + "date": "2024-01-16", + "count": 12 + } + ], + "averagePerDay": 10.5, + "weekday": [ + { + "dayOfWeek": 2, + "dayName": "Понедельник", + "count": 25, + "average": "6.25" + }, + { + "dayOfWeek": 3, + "dayName": "Вторник", + "count": 30, + "average": "7.50" + } + ], + "total": 315, + "daysWithData": 30 + }, + "global": { + "daily": [ + { + "date": "2024-01-15", + "count": 45 + }, + { + "date": "2024-01-16", + "count": 52 + } + ], + "averagePerDay": 48.5, + "weekday": [ + { + "dayOfWeek": 2, + "dayName": "Понедельник", + "count": 120, + "average": "30.00" + }, + { + "dayOfWeek": 3, + "dayName": "Вторник", + "count": 135, + "average": "33.75" + } + ], + "total": 1455, + "daysWithData": 30, + "activeUsers": 5 + }, + "period": { + "from": "2024-01-01T00:00:00.000Z", + "to": "2024-01-31T23:59:59.999Z" + } + } +} +``` + +**Структура ответа**: + +**`user`** — статистика текущего пользователя: +- `daily` — массив с количеством сигарет по дням + - `date` — дата в формате YYYY-MM-DD + - `count` — количество сигарет +- `averagePerDay` — среднее количество сигарет в день (число с плавающей точкой) +- `weekday` — статистика по дням недели + - `dayOfWeek` — номер дня недели (1 = воскресенье, 2 = понедельник, ..., 7 = суббота) + - `dayName` — название дня недели + - `count` — общее количество сигарет в этот день недели за весь период + - `average` — среднее количество за один такой день недели (строка) +- `total` — общее количество сигарет за период +- `daysWithData` — количество дней, в которые были записи + +**`global`** — общая статистика по всем **активным** пользователям: +- `daily` — массив с суммарным количеством сигарет всех активных пользователей по дням +- `averagePerDay` — среднее количество сигарет в день (активные пользователи) +- `weekday` — статистика по дням недели (активные пользователи) +- `total` — общее количество сигарет всех активных пользователей за период +- `daysWithData` — количество дней с записями +- `activeUsers` — количество активных пользователей в период + +> **Примечание**: Активными считаются только пользователи, которые в среднем выкуривают **от 2 до 40 сигарет в день**. Это позволяет исключить из статистики: +> - Тестовые аккаунты и неактивных пользователей (< 2 сигарет/день) +> - Ошибочные или накликанные данные (> 40 сигарет/день) + +**`period`** — информация о запрошенном периоде: +- `from` — начало периода (ISO 8601) +- `to` — конец периода (ISO 8601) + +**Особенности**: + +- Дни недели нумеруются по стандарту MongoDB: 1 = Воскресенье, 2 = Понедельник, ..., 7 = Суббота +- `average` для дней недели рассчитывается делением общего количества на количество таких дней в периоде +- Дни без записей **не включаются** в массив `daily` +- Глобальная статистика позволяет сравнить свои результаты с другими пользователями + +**Примеры использования**: + +```javascript +// Получение сводной статистики +const response = await fetch('http://localhost:8044/smoke-tracker/stats/summary', { + headers: { + 'Authorization': `Bearer ${token}` + } +}); + +const { body } = await response.json(); + +console.log(`Вы в среднем выкуриваете ${body.user.averagePerDay} сигарет в день`); +console.log(`Общее среднее по всем пользователям: ${body.global.averagePerDay} сигарет в день`); +console.log(`Активных пользователей в периоде: ${body.global.activeUsers}`); + +// Поиск самого "тяжёлого" дня недели +const maxWeekday = body.user.weekday.reduce((max, day) => + parseFloat(day.average) > parseFloat(max.average) ? day : max +); +console.log(`Больше всего вы курите в ${maxWeekday.dayName} (в среднем ${maxWeekday.average} сигарет)`); +``` + +**Визуализация данных по дням недели**: + +```javascript +// Данные для круговой диаграммы (Chart.js) +const weekdayChartData = { + labels: body.user.weekday.map(d => d.dayName), + datasets: [{ + label: 'Сигарет в день недели', + data: body.user.weekday.map(d => d.count), + backgroundColor: [ + 'rgba(255, 99, 132, 0.6)', + 'rgba(54, 162, 235, 0.6)', + 'rgba(255, 206, 86, 0.6)', + 'rgba(75, 192, 192, 0.6)', + 'rgba(153, 102, 255, 0.6)', + 'rgba(255, 159, 64, 0.6)', + 'rgba(199, 199, 199, 0.6)' + ] + }] +}; +``` + +**Сравнение с глобальной статистикой**: + +```javascript +// Сравнительный график (ваши данные vs общие данные) +const comparisonData = { + labels: body.user.weekday.map(d => d.dayName), + datasets: [ + { + label: 'Вы', + data: body.user.weekday.map(d => parseFloat(d.average)), + borderColor: 'rgb(255, 99, 132)', + backgroundColor: 'rgba(255, 99, 132, 0.2)', + }, + { + label: 'Среднее по пользователям', + data: body.global.weekday.map(d => parseFloat(d.average)), + borderColor: 'rgb(54, 162, 235)', + backgroundColor: 'rgba(54, 162, 235, 0.2)', + } + ] +}; +``` + +**Возможные ошибки**: + +- **401 Unauthorized**: `"Требуется авторизация"` — не передан токен +- **401 Unauthorized**: `"Неверный или истекший токен авторизации"` — токен невалидный/просрочен + +--- + +## Общая структура ответов + +Все эндпоинты возвращают JSON в следующем формате: + +**Успешный ответ**: + +```json +{ + "success": true, + "body": { /* данные */ } +} +``` + +**Ответ с ошибкой**: + +```json +{ + "success": false, + "errors": "Описание ошибки" +} +``` + +или (при использовании глобального обработчика ошибок): + +```json +{ + "message": "Описание ошибки" +} +``` + +--- + +## Коды состояния HTTP + +| Код | Описание | +|-----|----------| +| **200 OK** | Запрос выполнен успешно | +| **400 Bad Request** | Некорректные данные в запросе | +| **401 Unauthorized** | Требуется авторизация или токен невалидный | +| **500 Internal Server Error** | Внутренняя ошибка сервера | + +--- + +## Примеры интеграции + +### React + Axios + +```javascript +import axios from 'axios'; + +const API_BASE_URL = 'http://localhost:8044/smoke-tracker'; + +// Создание экземпляра axios с базовыми настройками +const api = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json' + } +}); + +// Интерцептор для добавления токена +api.interceptors.request.use(config => { + const token = localStorage.getItem('smokeToken'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +// Регистрация +export const signup = async (login, password) => { + const { data } = await api.post('/auth/signup', { login, password }); + return data; +}; + +// Вход +export const signin = async (login, password) => { + const { data } = await api.post('/auth/signin', { login, password }); + if (data.success) { + localStorage.setItem('smokeToken', data.body.token); + } + return data; +}; + +// Выход +export const signout = () => { + localStorage.removeItem('smokeToken'); +}; + +// Записать сигарету +export const logCigarette = async (smokedAt = null, note = '') => { + const { data } = await api.post('/cigarettes', { smokedAt, note }); + return data; +}; + +// Получить список сигарет +export const getCigarettes = async (from = null, to = null) => { + const params = {}; + if (from) params.from = from; + if (to) params.to = to; + + const { data } = await api.get('/cigarettes', { params }); + return data; +}; + +// Получить дневную статистику +export const getDailyStats = async (from = null, to = null) => { + const params = {}; + if (from) params.from = from; + if (to) params.to = to; + + const { data } = await api.get('/stats/daily', { params }); + return data; +}; +``` + +### Vanilla JavaScript + Fetch + +```javascript +const API_BASE_URL = 'http://localhost:8044/smoke-tracker'; + +// Получение токена +const getToken = () => localStorage.getItem('smokeToken'); + +// Базовый запрос +const apiRequest = async (endpoint, options = {}) => { + const token = getToken(); + + const headers = { + 'Content-Type': 'application/json', + ...options.headers + }; + + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + ...options, + headers + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message || error.errors || 'Ошибка запроса'); + } + + return response.json(); +}; + +// Регистрация +async function signup(login, password) { + return apiRequest('/auth/signup', { + method: 'POST', + body: JSON.stringify({ login, password }) + }); +} + +// Вход +async function signin(login, password) { + const data = await apiRequest('/auth/signin', { + method: 'POST', + body: JSON.stringify({ login, password }) + }); + + if (data.success) { + localStorage.setItem('smokeToken', data.body.token); + } + + return data; +} + +// Записать сигарету +async function logCigarette(note = '') { + return apiRequest('/cigarettes', { + method: 'POST', + body: JSON.stringify({ note }) + }); +} + +// Получить дневную статистику +async function getDailyStats() { + return apiRequest('/stats/daily'); +} +``` + +--- + +## Рекомендации по безопасности + +1. **Хранение токена**: + - Для веб-приложений: используйте `httpOnly` cookies или `sessionStorage` + - Избегайте `localStorage` при работе с чувствительными данными + - Для мобильных приложений: используйте безопасное хранилище (Keychain/Keystore) + +2. **HTTPS**: В production всегда используйте HTTPS для защиты токена при передаче + +3. **Обработка истечения токена**: + - Токен действителен 12 часов + - При получении ошибки 401 перенаправляйте пользователя на страницу входа + - Реализуйте механизм refresh token для бесшовного обновления + +4. **Валидация на фронтенде**: + - Проверяйте корректность email/логина перед отправкой + - Требуйте минимальную длину пароля (8+ символов) + - Показывайте индикатор силы пароля + +--- + +## Postman-коллекция + +Готовая коллекция для тестирования доступна в файле: + +``` +server/routers/smoke-tracker/postman/smoke-tracker.postman_collection.json +``` + +Импортируйте её в Postman для быстрого тестирования всех эндпоинтов. + +--- + +## Поддержка + +При возникновении вопросов или обнаружении проблем обращайтесь к разработчикам backend-команды. + diff --git a/server/routers/smoke-tracker/auth.js b/server/routers/smoke-tracker/auth.js new file mode 100644 index 0000000..32dbeed --- /dev/null +++ b/server/routers/smoke-tracker/auth.js @@ -0,0 +1,87 @@ +const { Router } = require('express') +const hash = require('pbkdf2-password')() +const { promisify } = require('node:util') +const jwt = require('jsonwebtoken') + +const { getAnswer } = require('../../utils/common') + +const { SmokeAuthModel } = require('./model/auth') +const { SmokeUserModel } = require('./model/user') +const { SMOKE_TRACKER_TOKEN_KEY } = require('./const') +const { requiredValidate } = require('./utils') + +const router = Router() + +router.post( + '/signup', + requiredValidate('login', 'password'), + async (req, res, next) => { + const { login, password } = req.body + + const existing = await SmokeAuthModel.findOne({ login }) + + if (existing) { + throw new Error('Пользователь с таким логином уже существует') + } + + hash({ password }, async function (err, pass, salt, hashValue) { + if (err) return next(err) + + const user = await SmokeUserModel.create({ login }) + await SmokeAuthModel.create({ login, hash: hashValue, salt, userId: user.id }) + + res.json(getAnswer(null, { ok: true })) + }) + } +) + +function authenticate(login, pass, cb) { + SmokeAuthModel.findOne({ login }) + .populate('userId') + .exec() + .then((user) => { + if (!user) return cb(null, null) + + hash({ password: pass, salt: user.salt }, function (err, pass, salt, hashValue) { + if (err) return cb(err) + if (hashValue === user.hash) return cb(null, user) + cb(null, null) + }) + }) + .catch((err) => cb(err)) +} + +const auth = promisify(authenticate) + +router.post( + '/signin', + requiredValidate('login', 'password'), + async (req, res) => { + const { login, password } = req.body + + const user = await auth(login, password) + + if (!user) { + throw new Error('Неверный логин или пароль') + } + + const accessToken = jwt.sign( + { + ...JSON.parse(JSON.stringify(user.userId)), + }, + SMOKE_TRACKER_TOKEN_KEY + // Для этого проекта токен делаем бессрочным (без поля expiresIn) + ) + + res.json( + getAnswer(null, { + user: user.userId, + token: accessToken, + }) + ) + } +) + +module.exports = router + + diff --git a/server/routers/smoke-tracker/cigarettes.js b/server/routers/smoke-tracker/cigarettes.js new file mode 100644 index 0000000..0851dc0 --- /dev/null +++ b/server/routers/smoke-tracker/cigarettes.js @@ -0,0 +1,76 @@ +const { Router } = require('express') +const mongoose = require('mongoose') + +const { getAnswer } = require('../../utils/common') +const { CigaretteModel } = require('./model/cigarette') +const { authMiddleware } = require('./middleware/auth') + +const router = Router() + +// Все эндпоинты ниже требуют авторизации +router.use(authMiddleware) + +// Логирование одной сигареты +router.post('/', async (req, res, next) => { + try { + const { smokedAt, note } = req.body || {} + const user = req.user + + let date + if (smokedAt) { + const parsed = new Date(smokedAt) + if (Number.isNaN(parsed.getTime())) { + throw new Error('Некорректный формат даты smokedAt') + } + date = parsed + } else { + date = new Date() + } + + const item = await CigaretteModel.create({ + userId: new mongoose.Types.ObjectId(user.id), + smokedAt: date, + note, + }) + + res.json(getAnswer(null, item)) + } catch (err) { + next(err) + } +}) + +// Получение списка сигарет пользователя (для отладки и таблиц) +router.get('/', async (req, res, next) => { + try { + const user = req.user + const { from, to } = req.query + + const filter = { userId: new mongoose.Types.ObjectId(user.id) } + + if (from || to) { + filter.smokedAt = {} + if (from) { + const fromDate = new Date(from) + if (!Number.isNaN(fromDate.getTime())) { + filter.smokedAt.$gte = fromDate + } + } + if (to) { + const toDate = new Date(to) + if (!Number.isNaN(toDate.getTime())) { + filter.smokedAt.$lte = toDate + } + } + } + + const items = await CigaretteModel.find(filter).sort({ smokedAt: 1 }) + + res.json(getAnswer(null, items)) + } catch (err) { + next(err) + } +}) + +module.exports = router + + diff --git a/server/routers/smoke-tracker/const.js b/server/routers/smoke-tracker/const.js new file mode 100644 index 0000000..1b089d8 --- /dev/null +++ b/server/routers/smoke-tracker/const.js @@ -0,0 +1,9 @@ +exports.SMOKE_TRACKER_USER_MODEL_NAME = 'SMOKE_TRACKER_USER' +exports.SMOKE_TRACKER_AUTH_MODEL_NAME = 'SMOKE_TRACKER_AUTH' +exports.SMOKE_TRACKER_CIGARETTE_MODEL_NAME = 'SMOKE_TRACKER_CIGARETTE' + +exports.SMOKE_TRACKER_TOKEN_KEY = + process.env.SMOKE_TRACKER_TOKEN_KEY || + 'smoke-tracker-secret-key-change-me' + + diff --git a/server/routers/smoke-tracker/index.js b/server/routers/smoke-tracker/index.js new file mode 100644 index 0000000..f3558c6 --- /dev/null +++ b/server/routers/smoke-tracker/index.js @@ -0,0 +1,13 @@ +const router = require('express').Router() + +const authRouter = require('./auth') +const cigarettesRouter = require('./cigarettes') +const statsRouter = require('./stats') + +router.use('/auth', authRouter) +router.use('/cigarettes', cigarettesRouter) +router.use('/stats', statsRouter) + +module.exports = router + + diff --git a/server/routers/smoke-tracker/middleware/auth.js b/server/routers/smoke-tracker/middleware/auth.js new file mode 100644 index 0000000..d762641 --- /dev/null +++ b/server/routers/smoke-tracker/middleware/auth.js @@ -0,0 +1,26 @@ +const jwt = require('jsonwebtoken') + +const { SMOKE_TRACKER_TOKEN_KEY } = require('../const') + +const authMiddleware = (req, res, next) => { + const authHeader = req.headers.authorization || '' + const token = authHeader.startsWith('Bearer ') + ? authHeader.slice(7) + : null + + if (!token) { + throw new Error('Требуется авторизация') + } + + try { + const decoded = jwt.verify(token, SMOKE_TRACKER_TOKEN_KEY) + req.user = decoded + next() + } catch (e) { + throw new Error('Неверный или истекший токен авторизации') + } +} + +module.exports.authMiddleware = authMiddleware + + diff --git a/server/routers/smoke-tracker/model/auth.js b/server/routers/smoke-tracker/model/auth.js new file mode 100644 index 0000000..7e701d5 --- /dev/null +++ b/server/routers/smoke-tracker/model/auth.js @@ -0,0 +1,33 @@ +const { Schema, model } = require('mongoose') + +const { + SMOKE_TRACKER_AUTH_MODEL_NAME, + SMOKE_TRACKER_USER_MODEL_NAME, +} = require('../const') + +const schema = new Schema({ + login: { type: String, required: true, unique: true }, + hash: { type: String, required: true }, + salt: { type: String, required: true }, + userId: { type: Schema.Types.ObjectId, ref: SMOKE_TRACKER_USER_MODEL_NAME }, + created: { + type: Date, + default: () => new Date().toISOString(), + }, +}) + +schema.set('toJSON', { + virtuals: true, + versionKey: false, + transform: function (doc, ret) { + delete ret._id + }, +}) + +schema.virtual('id').get(function () { + return this._id.toHexString() +}) + +exports.SmokeAuthModel = model(SMOKE_TRACKER_AUTH_MODEL_NAME, schema) + + diff --git a/server/routers/smoke-tracker/model/cigarette.js b/server/routers/smoke-tracker/model/cigarette.js new file mode 100644 index 0000000..768ac2d --- /dev/null +++ b/server/routers/smoke-tracker/model/cigarette.js @@ -0,0 +1,38 @@ +const { Schema, model } = require('mongoose') + +const { + SMOKE_TRACKER_CIGARETTE_MODEL_NAME, + SMOKE_TRACKER_USER_MODEL_NAME, +} = require('../const') + +const schema = new Schema({ + userId: { type: Schema.Types.ObjectId, ref: SMOKE_TRACKER_USER_MODEL_NAME, required: true }, + smokedAt: { + type: Date, + required: true, + default: () => new Date().toISOString(), + }, + note: { + type: String, + }, + created: { + type: Date, + default: () => new Date().toISOString(), + }, +}) + +schema.set('toJSON', { + virtuals: true, + versionKey: false, + transform: function (doc, ret) { + delete ret._id + }, +}) + +schema.virtual('id').get(function () { + return this._id.toHexString() +}) + +exports.CigaretteModel = model(SMOKE_TRACKER_CIGARETTE_MODEL_NAME, schema) + + diff --git a/server/routers/smoke-tracker/model/user.js b/server/routers/smoke-tracker/model/user.js new file mode 100644 index 0000000..221bbf7 --- /dev/null +++ b/server/routers/smoke-tracker/model/user.js @@ -0,0 +1,27 @@ +const { Schema, model } = require('mongoose') + +const { SMOKE_TRACKER_USER_MODEL_NAME } = require('../const') + +const schema = new Schema({ + login: { type: String, required: true, unique: true }, + created: { + type: Date, + default: () => new Date().toISOString(), + }, +}) + +schema.set('toJSON', { + virtuals: true, + versionKey: false, + transform: function (doc, ret) { + delete ret._id + }, +}) + +schema.virtual('id').get(function () { + return this._id.toHexString() +}) + +exports.SmokeUserModel = model(SMOKE_TRACKER_USER_MODEL_NAME, schema) + + diff --git a/server/routers/smoke-tracker/postman/smoke-tracker.postman_collection.json b/server/routers/smoke-tracker/postman/smoke-tracker.postman_collection.json new file mode 100644 index 0000000..b8f117f --- /dev/null +++ b/server/routers/smoke-tracker/postman/smoke-tracker.postman_collection.json @@ -0,0 +1,244 @@ +{ + "info": { + "_postman_id": "9d74101d-f788-4dbf-83b3-11c8f9789b73", + "name": "Smoke Tracker", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "smoke-tracker" + }, + "item": [ + { + "name": "Auth • Signup", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"login\": \"smoker-demo\",\n \"password\": \"secret123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/smoke-tracker/auth/signup", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "smoke-tracker", + "auth", + "signup" + ] + }, + "description": "Регистрация нового пользователя. Повторный вызов с тем же логином вернёт ошибку." + }, + "response": [] + }, + { + "name": "Auth • Signin", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "const json = pm.response.json();", + "if (json && json.body && json.body.token) {", + " pm.environment.set('smokeToken', json.body.token);", + "}" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"login\": \"smoker-demo\",\n \"password\": \"secret123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/smoke-tracker/auth/signin", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "smoke-tracker", + "auth", + "signin" + ] + }, + "description": "Авторизация пользователя. Скрипт тестов сохранит JWT в переменную окружения smokeToken." + }, + "response": [] + }, + { + "name": "Cigarettes • Log entry", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "name": "Authorization", + "value": "Bearer {{smokeToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"smokedAt\": \"2025-01-01T09:30:00.000Z\",\n \"note\": \"Первая сигарета за день\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/smoke-tracker/cigarettes", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "smoke-tracker", + "cigarettes" + ] + }, + "description": "Создать запись о выкуренной сигарете. Если smokedAt не указан, сервер использует текущее время." + }, + "response": [] + }, + { + "name": "Cigarettes • List", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "name": "Authorization", + "value": "Bearer {{smokeToken}}", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/smoke-tracker/cigarettes?from=2025-01-01T00:00:00.000Z&to=2025-01-07T23:59:59.999Z", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "smoke-tracker", + "cigarettes" + ], + "query": [ + { + "key": "from", + "value": "2025-01-01T00:00:00.000Z" + }, + { + "key": "to", + "value": "2025-01-07T23:59:59.999Z" + } + ] + }, + "description": "Список сигарет текущего пользователя. Параметры from/to необязательны." + }, + "response": [] + }, + { + "name": "Stats • Daily", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "name": "Authorization", + "value": "Bearer {{smokeToken}}", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/smoke-tracker/stats/daily?from=2025-01-01&to=2025-01-31", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "smoke-tracker", + "stats", + "daily" + ], + "query": [ + { + "key": "from", + "value": "2025-01-01" + }, + { + "key": "to", + "value": "2025-01-31" + } + ] + }, + "description": "Агрегация по дням для графиков. Если from/to не заданы, используется последний месяц." + }, + "response": [] + }, + { + "name": "Stats • Summary", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "name": "Authorization", + "value": "Bearer {{smokeToken}}", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/smoke-tracker/stats/summary?from=2025-01-01T00:00:00.000Z&to=2025-01-31T23:59:59.999Z", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "smoke-tracker", + "stats", + "summary" + ], + "query": [ + { + "key": "from", + "value": "2025-01-01T00:00:00.000Z" + }, + { + "key": "to", + "value": "2025-01-31T23:59:59.999Z" + } + ] + }, + "description": "Расширенная статистика: среднее в день, статистика по дням недели, сравнение с общими показателями всех пользователей." + }, + "response": [] + } + ], + "event": [], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8044" + }, + { + "key": "smokeToken", + "value": "" + } + ] +} + diff --git a/server/routers/smoke-tracker/stats.js b/server/routers/smoke-tracker/stats.js new file mode 100644 index 0000000..2d135b7 --- /dev/null +++ b/server/routers/smoke-tracker/stats.js @@ -0,0 +1,279 @@ +const { Router } = require('express') +const mongoose = require('mongoose') + +const { getAnswer } = require('../../utils/common') +const { CigaretteModel } = require('./model/cigarette') +const { authMiddleware } = require('./middleware/auth') + +const router = Router() + +// Все эндпоинты статистики требуют авторизации +router.use(authMiddleware) + +// Агрегация по дням: количество сигарет в день для построения графика +router.get('/daily', async (req, res, next) => { + try { + const user = req.user + const { from, to } = req.query + + const now = new Date() + const defaultFrom = new Date(now) + defaultFrom.setDate(defaultFrom.getDate() - 30) + + const fromDate = from ? new Date(from) : defaultFrom + const toDate = to ? new Date(to) : now + + const match = { + userId: new mongoose.Types.ObjectId(user.id), + smokedAt: { + $gte: fromDate, + $lte: toDate, + }, + } + + // Отладка: проверяем, сколько записей попадает в фильтр + const totalCount = await CigaretteModel.countDocuments(match) + console.log('[STATS] Match filter:', JSON.stringify(match, null, 2)) + console.log('[STATS] Total cigarettes in range:', totalCount) + + const data = await CigaretteModel.aggregate([ + { $match: match }, + { + $group: { + _id: { + $dateToString: { format: '%Y-%m-%d', date: '$smokedAt', timezone: 'UTC' }, + }, + count: { $sum: 1 }, + }, + }, + { $sort: { _id: 1 } }, + ]) + + console.log('[STATS] Aggregation result:', data) + + const result = data.map((item) => ({ + date: item._id, + count: item.count, + })) + + res.json(getAnswer(null, result)) + } catch (err) { + next(err) + } +}) + +// Сводная статистика: среднее в день, по дням недели, общее по всем пользователям +router.get('/summary', async (req, res, next) => { + try { + const user = req.user + const { from, to } = req.query + + const now = new Date() + const defaultFrom = new Date(now) + defaultFrom.setDate(defaultFrom.getDate() - 30) + + const fromDate = from ? new Date(from) : defaultFrom + const toDate = to ? new Date(to) : now + + // Фильтр для текущего пользователя + const userMatch = { + userId: new mongoose.Types.ObjectId(user.id), + smokedAt: { + $gte: fromDate, + $lte: toDate, + }, + } + + // Фильтр для всех пользователей (общая статистика) + const globalMatch = { + smokedAt: { + $gte: fromDate, + $lte: toDate, + }, + } + + // 1. Статистика по дням (для текущего пользователя) + const dailyStats = await CigaretteModel.aggregate([ + { $match: userMatch }, + { + $group: { + _id: { + $dateToString: { format: '%Y-%m-%d', date: '$smokedAt', timezone: 'UTC' }, + }, + count: { $sum: 1 }, + }, + }, + { $sort: { _id: 1 } }, + ]) + + const dailyData = dailyStats.map((item) => ({ + date: item._id, + count: item.count, + })) + + // 2. Среднее количество в день (для текущего пользователя) + const totalCigarettes = dailyStats.reduce((sum, item) => sum + item.count, 0) + const daysWithData = dailyStats.length + const averagePerDay = daysWithData > 0 ? (totalCigarettes / daysWithData).toFixed(2) : 0 + + // 3. Статистика по дням недели (для текущего пользователя) + const weekdayStats = await CigaretteModel.aggregate([ + { $match: userMatch }, + { + $group: { + _id: { $dayOfWeek: '$smokedAt' }, // 1 = воскресенье, 2 = понедельник, ..., 7 = суббота + count: { $sum: 1 }, + }, + }, + { $sort: { _id: 1 } }, + ]) + + const weekdayNames = ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'] + const weekdayData = weekdayStats.map((item) => { + const dayIndex = item._id - 1 // MongoDB возвращает 1-7, приводим к 0-6 + return { + dayOfWeek: item._id, + dayName: weekdayNames[dayIndex], + count: item.count, + } + }) + + // Вычисляем среднее для каждого дня недели + const weekdayAverages = weekdayData.map((day) => { + // Считаем, сколько раз встречается этот день недели в периоде + const occurrences = Math.floor( + (toDate.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24 * 7) + ) + 1 + return { + ...day, + average: occurrences > 0 ? (day.count / occurrences).toFixed(2) : day.count, + } + }) + + // 4. Определяем активных пользователей (от 2 до 40 сигарет в день в среднем) + const MIN_CIGARETTES_PER_DAY = 2 + const MAX_CIGARETTES_PER_DAY = 40 + const periodDays = Math.ceil((toDate.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24)) + + // Получаем статистику по каждому пользователю + const userStats = await CigaretteModel.aggregate([ + { $match: globalMatch }, + { + $group: { + _id: '$userId', + total: { $sum: 1 }, + }, + }, + ]) + + // Фильтруем активных пользователей (исключаем слишком низкие и слишком высокие значения) + const activeUserIds = userStats + .filter((stat) => { + const avgPerDay = stat.total / periodDays + return avgPerDay > MIN_CIGARETTES_PER_DAY && avgPerDay <= MAX_CIGARETTES_PER_DAY + }) + .map((stat) => stat._id) + + const filteredLow = userStats.filter((stat) => stat.total / periodDays <= MIN_CIGARETTES_PER_DAY).length + const filteredHigh = userStats.filter((stat) => stat.total / periodDays > MAX_CIGARETTES_PER_DAY).length + + console.log('[STATS] Total users:', userStats.length) + console.log('[STATS] Active users (2-40 cigs/day):', activeUserIds.length) + console.log('[STATS] Filtered out (too low):', filteredLow) + console.log('[STATS] Filtered out (too high):', filteredHigh) + + // Фильтр только для активных пользователей + const activeGlobalMatch = { + ...globalMatch, + userId: { $in: activeUserIds }, + } + + // Общая статистика по активным пользователям + const globalDailyStats = await CigaretteModel.aggregate([ + { $match: activeGlobalMatch }, + { + $group: { + _id: { + $dateToString: { format: '%Y-%m-%d', date: '$smokedAt', timezone: 'UTC' }, + }, + count: { $sum: 1 }, + }, + }, + { $sort: { _id: 1 } }, + ]) + + const globalDailyData = globalDailyStats.map((item) => ({ + date: item._id, + count: item.count, + })) + + const globalTotalCigarettes = globalDailyStats.reduce((sum, item) => sum + item.count, 0) + const globalDaysWithData = globalDailyStats.length + const globalAveragePerDay = + globalDaysWithData > 0 ? (globalTotalCigarettes / globalDaysWithData).toFixed(2) : 0 + + // Общая статистика по дням недели (активные пользователи) + const globalWeekdayStats = await CigaretteModel.aggregate([ + { $match: activeGlobalMatch }, + { + $group: { + _id: { $dayOfWeek: '$smokedAt' }, + count: { $sum: 1 }, + }, + }, + { $sort: { _id: 1 } }, + ]) + + const globalWeekdayData = globalWeekdayStats.map((item) => { + const dayIndex = item._id - 1 + return { + dayOfWeek: item._id, + dayName: weekdayNames[dayIndex], + count: item.count, + } + }) + + const globalWeekdayAverages = globalWeekdayData.map((day) => { + const occurrences = Math.floor( + (toDate.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24 * 7) + ) + 1 + return { + ...day, + average: occurrences > 0 ? (day.count / occurrences).toFixed(2) : day.count, + } + }) + + // Количество активных пользователей + const activeUsers = activeUserIds + + const result = { + user: { + daily: dailyData, + averagePerDay: parseFloat(averagePerDay), + weekday: weekdayAverages, + total: totalCigarettes, + daysWithData, + }, + global: { + daily: globalDailyData, + averagePerDay: parseFloat(globalAveragePerDay), + weekday: globalWeekdayAverages, + total: globalTotalCigarettes, + daysWithData: globalDaysWithData, + activeUsers: activeUsers.length, + }, + period: { + from: fromDate.toISOString(), + to: toDate.toISOString(), + }, + } + + res.json(getAnswer(null, result)) + } catch (err) { + next(err) + } +}) + +module.exports = router + + diff --git a/server/routers/smoke-tracker/utils.js b/server/routers/smoke-tracker/utils.js new file mode 100644 index 0000000..4c24b41 --- /dev/null +++ b/server/routers/smoke-tracker/utils.js @@ -0,0 +1,21 @@ +const requiredValidate = + (...fields) => + (req, res, next) => { + const errors = [] + + fields.forEach((field) => { + if (!req.body[field]) { + errors.push(field) + } + }) + + if (errors.length) { + throw new Error(`Не все поля заполнены: ${errors.join(', ')}`) + } else { + next() + } + } + +module.exports.requiredValidate = requiredValidate + + diff --git a/server/server.ts b/server/server.ts new file mode 100644 index 0000000..801959b --- /dev/null +++ b/server/server.ts @@ -0,0 +1,1211 @@ +import fs from 'fs' +import path from 'path' +import { Router } from 'express' +import mongoose from 'mongoose' + +import pkg from '../package.json' + +import './utils/mongoose' +import { ErrorLog } from './models/ErrorLog' + +const folderPath = path.resolve(__dirname, './routers') + +const getUrl = (url) => `${process.env.NODE_ENV === 'development' ? '' : '/ms'}${url}` + +// Определение типов +interface EndpointStats { + total: number; + mock: number; + real: number; +} + +interface FileInfo { + path: string; + type: 'file'; + endpoints: EndpointStats; +} + +interface DirectoryInfo { + path: string; + type: 'directory'; + endpoints: EndpointStats; + children: (FileInfo | DirectoryInfo)[]; +} + +interface DirScanResult { + items: (FileInfo | DirectoryInfo)[]; + totalEndpoints: EndpointStats; +} + +// Функция для поиска эндпоинтов в файлах +function countEndpoints(filePath) { + if (!fs.existsSync(filePath) || !filePath.endsWith('.js') && !filePath.endsWith('.ts')) { + return { total: 0, mock: 0, real: 0 }; + } + + try { + const content = fs.readFileSync(filePath, 'utf8'); + const httpMethods = ['get', 'post', 'put', 'delete', 'patch']; + + const endpointMatches = []; + let totalCount = 0; + + // Собираем все эндпоинты и их контекст + httpMethods.forEach(method => { + const regex = new RegExp(`router\\.${method}\\([^{]*{([\\s\\S]*?)(?:}\\s*\\)|},)`, 'gi'); + let match; + + while ((match = regex.exec(content)) !== null) { + totalCount++; + endpointMatches.push({ + method, + body: match[1] + }); + } + }); + + // Проверяем каждый эндпоинт - работает с БД или моковый + let mockCount = 0; + let realCount = 0; + + endpointMatches.forEach(endpoint => { + const body = endpoint.body; + + // Признаки работы с базой данных - модели Mongoose и их методы + const hasDbInteraction = /\b(find|findOne|findById|create|update|delete|remove|aggregate|count|model)\b.*\(/.test(body) || + /\bmongoose\b/.test(body) || + /\.[a-zA-Z]+Model\b/.test(body); + + // Проверка на отправку файлов (считается реальным эндпоинтом) + const hasFileServing = /res\.sendFile\(/.test(body); + + // Проверка на отправку ошибок в JSON (считается реальным эндпоинтом) + const hasErrorResponse = /res\.json\(\s*{.*?(error|success\s*:\s*false).*?}\)/.test(body) || + /res\.status\(.*?\)\.json\(/.test(body); + + // Признаки моковых данных - только явный импорт JSON файлов или отправка JSON без ошибок + const hasMockJsonImport = /require\s*\(\s*['"`].*\.json['"`]\s*\)/.test(body) || + /import\s+.*\s+from\s+['"`].*\.json['"`]/.test(body); + + // JSON ответ, который не является ошибкой (упрощенная проверка) + const hasJsonResponse = /res\.json\(/.test(body) && !hasErrorResponse; + + // Определяем тип эндпоинта + if (hasDbInteraction || hasFileServing || hasErrorResponse) { + // Если работает с БД, отправляет файлы или возвращает ошибки - считаем реальным + realCount++; + } else if (hasMockJsonImport || hasJsonResponse) { + // Если импортирует JSON или отправляет JSON без ошибок - считаем моком + mockCount++; + } else { + // По умолчанию считаем реальным + realCount++; + } + }); + + return { + total: totalCount, + mock: mockCount, + real: realCount + }; + } catch (err) { + return { total: 0, mock: 0, real: 0 }; + } +} + +// Функция для рекурсивного обхода директорий +function getAllDirs(dir, basePath = ''): DirScanResult { + const items: (FileInfo | DirectoryInfo)[] = []; + let totalEndpoints = { total: 0, mock: 0, real: 0 }; + + try { + const dirItems = fs.readdirSync(dir); + + for (const item of dirItems) { + const itemPath = path.join(dir, item); + const relativePath = path.join(basePath, item); + const stat = fs.statSync(itemPath); + + if (stat.isDirectory()) { + const dirResult = getAllDirs(itemPath, relativePath); + totalEndpoints.total += dirResult.totalEndpoints.total; + totalEndpoints.mock += dirResult.totalEndpoints.mock; + totalEndpoints.real += dirResult.totalEndpoints.real; + + items.push({ + path: relativePath, + type: 'directory', + endpoints: dirResult.totalEndpoints, + children: dirResult.items + }); + } else { + const fileEndpoints = countEndpoints(itemPath); + totalEndpoints.total += fileEndpoints.total; + totalEndpoints.mock += fileEndpoints.mock; + totalEndpoints.real += fileEndpoints.real; + + items.push({ + path: relativePath, + type: 'file', + endpoints: fileEndpoints + }); + } + } + } catch (err) { + console.error(`Ошибка при чтении директории ${dir}:`, err); + } + + return { + items, + totalEndpoints + }; +} + +// Функция для генерации HTML-списка директорий +function generateDirList(dirs: (FileInfo | DirectoryInfo)[], level = 0) { + if (dirs.length === 0) return ''; + + const indent = level * 20; + + return `
    ${dirs.map(item => { + if (item.type === 'directory') { + const endpointClass = item.endpoints.total > 0 ? 'has-endpoints' : ''; + const endpointInfo = item.endpoints.total > 0 + ? `
    + ${item.endpoints.total} + ${item.endpoints.mock} + ${item.endpoints.real} +
    ` + : ''; + + return ` +
  • +
    + + ${path.basename(item.path)} + ${endpointInfo} +
    + +
  • + `; + } else { + const endpointClass = item.endpoints.total > 0 ? 'has-endpoints' : ''; + const endpointInfo = item.endpoints.total > 0 + ? `
    + ${item.endpoints.total} + ${item.endpoints.mock} + ${item.endpoints.real} +
    ` + : ''; + + return ` +
  • +
    + 📄 + ${path.basename(item.path)} + ${endpointInfo} +
    +
  • + `; + } + }).join('')}
`; +} + +const router = Router() + +// Эндпоинт для получения содержимого файла +router.get('/file-content', async (req, res) => { + try { + const filePath = req.query.path as string; + if (!filePath) { + return res.status(400).json({ error: 'Путь к файлу не указан' }); + } + + // Используем корень проекта для получения абсолютного пути к файлу + const projectRoot = path.resolve(__dirname, 'routers'); + const absolutePath = path.join(projectRoot, filePath); + + // Проверка, что путь не выходит за пределы проекта + if (!absolutePath.startsWith(projectRoot)) { + return res.status(403).json({ error: 'Доступ запрещен' }); + } + + if (!fs.existsSync(absolutePath)) { + return res.status(404).json({ error: 'Файл не найден' }); + } + + // Проверяем, что это файл, а не директория + const stat = fs.statSync(absolutePath); + if (!stat.isFile()) { + return res.status(400).json({ error: 'Указанный путь не является файлом' }); + } + + // Проверяем размер файла, чтобы не загружать слишком большие файлы + const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB + if (stat.size > MAX_FILE_SIZE) { + return res.status(413).json({ + error: 'Файл слишком большой', + size: stat.size, + maxSize: MAX_FILE_SIZE + }); + } + + const content = fs.readFileSync(absolutePath, 'utf8'); + const fileExtension = path.extname(absolutePath).slice(1); + + res.json({ + content, + extension: fileExtension, + fileName: path.basename(absolutePath) + }); + } catch (err) { + console.error('Ошибка при чтении файла:', err); + res.status(500).json({ error: 'Ошибка при чтении файла' }); + } +}); + +router.get('/', async (req, res) => { + // throw new Error('check error message') + // Используем корень проекта вместо только директории routers + const projectRoot = path.resolve(__dirname, 'routers'); + const routersPath = path.resolve(__dirname, 'routers'); + const routerFolders = fs.readdirSync(routersPath); + + const directoryResult = getAllDirs(projectRoot); + const totalEndpoints = directoryResult.totalEndpoints.total; + const mockEndpoints = directoryResult.totalEndpoints.mock; + const realEndpoints = directoryResult.totalEndpoints.real; + + // Получаем последние 10 ошибок + const latestErrors = await ErrorLog.find().sort({ createdAt: -1 }).limit(10); + + // Сформируем HTML для секции с ошибками + let errorsHtml = ''; + if (latestErrors.length > 0) { + errorsHtml = latestErrors.map(error => ` +
+
+

${error.message}

+ ${new Date(error.createdAt).toLocaleString()} +
+
+ ${error.path ? `
${error.method || 'GET'} ${error.path}
` : ''} + ${error.stack ? `
${error.stack}
` : ''} +
+
+ `).join(''); + } else { + errorsHtml = '

Нет зарегистрированных ошибок

'; + } + + // Создаем JavaScript для клиентской части + const clientScript = ` + document.addEventListener('DOMContentLoaded', function() { + // Директории + document.querySelectorAll('.dir-item[data-expandable="true"]').forEach(item => { + item.addEventListener('click', function(e) { + const subdirectory = this.nextElementSibling; + const isExpanded = this.classList.toggle('expanded'); + + if (isExpanded) { + subdirectory.style.display = 'block'; + this.querySelector('.expand-icon').textContent = '▼'; + } else { + subdirectory.style.display = 'none'; + this.querySelector('.expand-icon').textContent = '▶'; + } + + e.stopPropagation(); + }); + }); + + // Модальное окно + const modal = document.getElementById('fileModal'); + const closeBtn = document.querySelector('.close-modal'); + const fileContent = document.getElementById('fileContent'); + const fileLoader = document.getElementById('fileLoader'); + const modalFileName = document.getElementById('modalFileName'); + + // Закрытие модального окна + closeBtn.addEventListener('click', function() { + modal.style.display = 'none'; + }); + + window.addEventListener('click', function(event) { + if (event.target == modal) { + modal.style.display = 'none'; + } + }); + + // Обработчик для файлов + document.querySelectorAll('.file-item').forEach(item => { + item.addEventListener('click', async function() { + const filePath = this.getAttribute('data-path'); + if (!filePath) return; + + // Показываем модальное окно и лоадер + modal.style.display = 'block'; + fileContent.style.display = 'none'; + fileLoader.style.display = 'block'; + modalFileName.textContent = 'Загрузка...'; + + try { + const response = await fetch('${getUrl('/file-content?path=')}' + encodeURIComponent(filePath)); + if (!response.ok) { + throw new Error('Ошибка при загрузке файла'); + } + + const data = await response.json(); + + // Отображаем содержимое файла + fileLoader.style.display = 'none'; + fileContent.style.display = 'block'; + fileContent.textContent = data.content; + modalFileName.textContent = data.fileName; + + // Подсветка синтаксиса + const extensionMap = { + 'js': 'javascript', + 'ts': 'typescript', + 'json': 'json', + 'css': 'css', + 'html': 'xml', + 'xml': 'xml', + 'md': 'markdown', + 'yaml': 'yaml', + 'yml': 'yaml', + 'sh': 'bash', + 'bash': 'bash' + }; + + const language = extensionMap[data.extension] || ''; + if (language) { + fileContent.className = 'language-' + language; + hljs.highlightElement(fileContent); + } + } catch (error) { + fileLoader.style.display = 'none'; + fileContent.style.display = 'block'; + fileContent.textContent = 'Ошибка при загрузке файла: ' + error.message; + modalFileName.textContent = 'Ошибка'; + } + }); + }); + + // Обработчик кнопки очистки ошибок + const clearErrorsBtn = document.getElementById('clearErrorsBtn'); + const successAction = document.getElementById('successAction'); + + clearErrorsBtn.addEventListener('click', async function() { + try { + const response = await fetch('${getUrl('/clear-old-errors')}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }); + + if (!response.ok) { + throw new Error('Ошибка при очистке старых ошибок'); + } + + const data = await response.json(); + + // Показываем сообщение об успехе + successAction.textContent = 'Удалено ' + data.deletedCount + ' записей'; + successAction.style.display = 'block'; + + // Перезагружаем страницу через 2 секунды + setTimeout(() => { + window.location.reload(); + }, 2000); + + } catch (error) { + console.error('Ошибка:', error); + alert('Произошла ошибка: ' + error.message); + } + }); + + // Обработчик кнопок очистки коллекций + document.querySelectorAll('.clear-model-btn').forEach(button => { + button.addEventListener('click', async function() { + const modelName = this.getAttribute('data-model'); + if (!modelName) return; + + if (!confirm(\`Вы уверены, что хотите очистить коллекцию \${modelName}?\`)) { + return; + } + + try { + const response = await fetch('' + getUrl('/clear-collection'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ modelName }) + }); + + if (!response.ok) { + throw new Error('Ошибка при очистке коллекции'); + } + + const data = await response.json(); + + // Показываем сообщение об успехе + successAction.textContent = 'Коллекция ' + data.model + ' очищена. Удалено ' + data.deletedCount + ' записей'; + successAction.style.display = 'block'; + + // Перезагружаем страницу через 2 секунды + setTimeout(() => { + window.location.reload(); + }, 2000); + + } catch (error) { + console.error('Ошибка:', error); + alert('Произошла ошибка: ' + error.message); + } + }); + }); + }); + `; + + res.send(` + + + + Multy Stub v${pkg.version} + + + + + + + + + + + + + + + + +
+
+

Multy Stub v${pkg.version}

+
+
+
${totalEndpoints}
+
Всего эндпоинтов
+
+
+
${mockEndpoints}
+
Моковые эндпоинты
+
+
+
${realEndpoints}
+
Реальные эндпоинты
+
+
+
+ +
+

Routers:

+
+ ${routerFolders.map((f) => ` +
+
${f}
+
+ `).join('')} +
+
+ +
+

Структура директорий проекта:

+ ${generateDirList(directoryResult.items)} +
+ +
+

Models:

+
+ ${ + (await Promise.all( + (await mongoose.modelNames()).map(async (name) => { + const model = mongoose.model(name); + const count = await model.countDocuments(); + // Получаем информацию о полях модели + const schema = model.schema; + const fields = Object.keys(schema.paths).filter(field => !['__v', '_id'].includes(field)); + + return ` +
+
+

${name}

+

${count} документов

+
+
+
+
+
+
+

Поля:

+
    + ${fields.map(field => { + const fieldType = schema.paths[field].instance; + return `
  • ${field}: ${fieldType}
  • `; + }).join('')} +
+
+
+ +
+
+
+ `; + } + ) + )).join('') + } +
+
+ +
+

Последние ошибки:

+ +
+ ${errorsHtml} +
+
+
+ + + + +
Операция выполнена успешно
+ + + + + `) +}) + +// Эндпоинт для очистки ошибок старше 10 дней +router.post('/clear-old-errors', async (req, res) => { + try { + const tenDaysAgo = new Date(); + tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); + + const result = await ErrorLog.deleteMany({ createdAt: { $lt: tenDaysAgo } }); + + res.json({ + success: true, + deletedCount: result.deletedCount || 0 + }); + } catch (error) { + console.error('Ошибка при очистке старых ошибок:', error); + res.status(500).json({ + success: false, + error: 'Ошибка при очистке старых ошибок' + }); + } +}); + +// Эндпоинт для очистки отдельной коллекции +router.post('/clear-collection', async (req, res) => { + try { + const { modelName } = req.body; + + if (!modelName) { + return res.status(400).json({ + success: false, + error: 'Имя модели не указано' + }); + } + + // Проверяем, существует ли такая модель + if (!mongoose.modelNames().includes(modelName)) { + return res.status(404).json({ + success: false, + error: 'Модель не найдена' + }); + } + + const model = mongoose.model(modelName); + const result = await model.deleteMany({}); + + res.json({ + success: true, + deletedCount: result.deletedCount || 0, + model: modelName + }); + } catch (error) { + console.error(`Ошибка при очистке коллекции:`, error); + res.status(500).json({ + success: false, + error: 'Ошибка при очистке коллекции' + }); + } +}); + +export default router diff --git a/server/utils/common.js b/server/utils/common.ts similarity index 75% rename from server/utils/common.js rename to server/utils/common.ts index e0aee4f..42c19e7 100644 --- a/server/utils/common.js +++ b/server/utils/common.ts @@ -1,4 +1,4 @@ -exports.getAnswer = (errors, data, success = true) => { +export const getAnswer = (errors, data, success = true) => { if (errors) { return { success: false, @@ -12,7 +12,7 @@ exports.getAnswer = (errors, data, success = true) => { } } -exports.getResponse = (errors, data, success = true) => { +export const getResponse = (errors, data, success = true) => { if (errors.length) { return { success: false, diff --git a/server/utils/const.js b/server/utils/const.js deleted file mode 100644 index bc12a52..0000000 --- a/server/utils/const.js +++ /dev/null @@ -1,4 +0,0 @@ -const rc = require('../../.serverrc') - -// Connection URL -exports.mongoUrl = `mongodb://${rc.mongoAddr}:${rc.mongoPort}` diff --git a/server/utils/const.ts b/server/utils/const.ts new file mode 100644 index 0000000..70afa42 --- /dev/null +++ b/server/utils/const.ts @@ -0,0 +1,4 @@ +import 'dotenv/config'; + +// Connection URL +export const mongoUrl = process.env.MONGO_ADDR || 'mongodb://admin:password@localhost:27017/procurement_db?authSource=admin'; diff --git a/server/utils/mongo.js b/server/utils/mongo.ts similarity index 51% rename from server/utils/mongo.js rename to server/utils/mongo.ts index 258067c..ea2d6d0 100644 --- a/server/utils/mongo.js +++ b/server/utils/mongo.ts @@ -1,17 +1,18 @@ -const MDBClient = require('mongodb').MongoClient +import { MongoClient as MDBClient } from 'mongodb' -const { mongoUrl } = require('./const') +import { mongoUrl } from './const' const dbInstanses = { } const mongoDBConnect = async () => { try { - const MongoClient = new MDBClient(mongoUrl, { - useUnifiedTopology: true, - }) - return await MongoClient.connect() + const MongoClient = new MDBClient(mongoUrl) + const client = await MongoClient.connect() + console.log('Подключение к MongoDB успешно') + return client } catch (error) { + console.log('Неудачная попытка подключения к MongoDB') console.error(error) } } @@ -27,6 +28,6 @@ const getDB = async (dbName) => { } } -module.exports = { +export { getDB, } diff --git a/server/utils/mongoose.js b/server/utils/mongoose.js deleted file mode 100644 index d11d7bc..0000000 --- a/server/utils/mongoose.js +++ /dev/null @@ -1,5 +0,0 @@ -const mongoose = require('mongoose') - -const { mongoUrl } = require('./const') - -mongoose.connect(`${mongoUrl}/mongoose`) diff --git a/server/utils/mongoose.ts b/server/utils/mongoose.ts new file mode 100644 index 0000000..34278ef --- /dev/null +++ b/server/utils/mongoose.ts @@ -0,0 +1,16 @@ +import mongoose from 'mongoose' + +import { mongoUrl } from './const' + +mongoose.connect(mongoUrl).then(() => { + console.log('Подключение к MongoDB успешно') +}).catch((err) => { + console.log('Неудачная попытка подключения к MongoDB') + console.error(err) +}) + +export default mongoose + +// Для совместимости с CommonJS +module.exports = mongoose +module.exports.default = mongoose diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b1980b8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,112 @@ +{ + "compilerOptions": { + /* Посетите https://aka.ms/tsconfig чтобы узнать больше об этом файле */ + + /* Проекты */ + // "incremental": true, /* Сохранять .tsbuildinfo файлы для инкрементальной компиляции проектов. */ + // "composite": true, /* Включить ограничения, которые позволяют использовать проект TypeScript со ссылками на проекты. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Указать путь к файлу инкрементальной компиляции .tsbuildinfo. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Отключить предпочтение исходных файлов вместо файлов объявлений при ссылке на составные проекты. */ + // "disableSolutionSearching": true, /* Исключить проект из проверки ссылок нескольких проектов при редактировании. */ + // "disableReferencedProjectLoad": true, /* Уменьшить количество проектов, загружаемых автоматически TypeScript. */ + + /* Язык и окружение */ + "target": "es2018", /* Установить версию языка JavaScript для сгенерированного JavaScript и включить совместимые объявления библиотек. */ + // "lib": [], /* Указать набор объединенных файлов объявлений библиотек, описывающих целевое окружение выполнения. */ + // "jsx": "preserve", /* Указать, какой JSX код генерируется. */ + // "experimentalDecorators": true, /* Включить экспериментальную поддержку устаревших экспериментальных декораторов. */ + // "emitDecoratorMetadata": true, /* Генерировать метаданные типов дизайна для декорированных объявлений в исходных файлах. */ + // "jsxFactory": "", /* Указать функцию фабрики JSX, используемую при таргетинге на React JSX, например 'React.createElement' или 'h'. */ + // "jsxFragmentFactory": "", /* Указать ссылку на JSX Fragment, используемую для фрагментов при таргетинге на React JSX, например 'React.Fragment' или 'Fragment'. */ + // "jsxImportSource": "", /* Указать спецификатор модуля, используемый для импорта функций фабрики JSX при использовании 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Указать объект, вызываемый для 'createElement'. Применяется только при таргетинге на 'react' JSX. */ + // "noLib": true, /* Отключить включение любых файлов библиотек, включая lib.d.ts по умолчанию. */ + // "useDefineForClassFields": true, /* Генерировать поля классов, совместимые со стандартом ECMAScript. */ + // "moduleDetection": "auto", /* Управлять методом, используемым для обнаружения JS файлов формата модулей. */ + + /* Модули */ + "module": "NodeNext", /* Указать, какой код модулей генерируется. */ + "rootDir": ".", /* Указать корневую папку в ваших исходных файлах. */ + "moduleResolution": "nodenext", /* Указать, как TypeScript ищет файл по заданному спецификатору модуля. */ + // "baseUrl": "./", /* Указать базовую директорию для разрешения неотносительных имен модулей. */ + // "paths": {}, /* Указать набор записей, которые переопределяют импорты для дополнительных мест поиска. */ + // "rootDirs": [], /* Разрешить обработку нескольких папок как одной при разрешении модулей. */ + // "typeRoots": [], /* Указать несколько папок, которые действуют как './node_modules/@types'. */ + // "types": [], /* Указать имена пакетов типов, которые должны быть включены без ссылки в исходном файле. */ + // "allowUmdGlobalAccess": true, /* Разрешить доступ к UMD глобальным переменным из модулей. */ + // "moduleSuffixes": [], /* Список суффиксов имен файлов для поиска при разрешении модуля. */ + // "allowImportingTsExtensions": true, /* Разрешить импорты с расширениями файлов TypeScript. Требует '--moduleResolution bundler' и либо '--noEmit', либо '--emitDeclarationOnly'. */ + "resolvePackageJsonExports": true, /* Использовать поле 'exports' из package.json при разрешении импортов пакетов. */ + // "resolvePackageJsonImports": true, /* Использовать поле 'imports' из package.json при разрешении импортов. */ + // "customConditions": [], /* Условия для установки в дополнение к специфичным для резолвера значениям по умолчанию при разрешении импортов. */ + "resolveJsonModule": true, /* Включить импорт .json файлов. */ + // "allowArbitraryExtensions": true, /* Включить импорт файлов с любым расширением, при условии наличия файла объявлений. */ + // "noResolve": true, /* Запретить 'import's, 'require's или ''s от расширения количества файлов, которые TypeScript должен добавить в проект. */ + + /* Поддержка JavaScript */ + "allowJs": true, /* Разрешить файлам JavaScript быть частью вашей программы. Используйте опцию 'checkJS' для получения ошибок из этих файлов. */ + // "checkJs": true, /* Включить сообщения об ошибках в файлах JavaScript с проверкой типов. */ + // "maxNodeModuleJsDepth": 1, /* Указать максимальную глубину папок, используемую для проверки JavaScript файлов из 'node_modules'. Применимо только с 'allowJs'. */ + + /* Генерация */ + // "declaration": true, /* Генерировать .d.ts файлы из файлов TypeScript и JavaScript в вашем проекте. */ + // "declarationMap": true, /* Создавать sourcemaps для d.ts файлов. */ + // "emitDeclarationOnly": true, /* Выводить только d.ts файлы, а не JavaScript файлы. */ + // "sourceMap": true, /* Создавать файлы source map для сгенерированных JavaScript файлов. */ + // "inlineSourceMap": true, /* Включать файлы sourcemap внутри сгенерированного JavaScript. */ + // "outFile": "./", /* Указать файл, который объединяет все выходные данные в один JavaScript файл. Если 'declaration' равно true, также обозначает файл, который объединяет весь .d.ts вывод. */ + "outDir": "./dist", /* Указать выходную папку для всех сгенерированных файлов. */ + // "removeComments": true, /* Отключить генерацию комментариев. */ + // "noEmit": true, /* Отключить генерацию файлов из компиляции. */ + // "importHelpers": true, /* Разрешить импорт вспомогательных функций из tslib один раз на проект, вместо включения их в каждый файл. */ + // "importsNotUsedAsValues": "remove", /* Указать поведение генерации/проверки для импортов, которые используются только для типов. */ + // "downlevelIteration": true, /* Генерировать более совместимый, но многословный и менее производительный JavaScript для итерации. */ + // "sourceRoot": "", /* Указать корневой путь для отладчиков для поиска эталонного исходного кода. */ + // "mapRoot": "", /* Указать местоположение, где отладчик должен найти map файлы вместо сгенерированных местоположений. */ + // "inlineSources": true, /* Включать исходный код в sourcemaps внутри сгенерированного JavaScript. */ + // "emitBOM": true, /* Генерировать UTF-8 Byte Order Mark (BOM) в начале выходных файлов. */ + // "newLine": "crlf", /* Установить символ новой строки для генерации файлов. */ + // "stripInternal": true, /* Отключить генерацию объявлений, которые имеют '@internal' в их JSDoc комментариях. */ + // "noEmitHelpers": true, /* Отключить генерацию пользовательских вспомогательных функций, таких как '__extends' в скомпилированном выводе. */ + // "noEmitOnError": true, /* Отключить генерацию файлов, если сообщается о любых ошибках проверки типов. */ + // "preserveConstEnums": true, /* Отключить стирание объявлений 'const enum' в сгенерированном коде. */ + // "declarationDir": "./", /* Указать выходную директорию для сгенерированных файлов объявлений. */ + // "preserveValueImports": true, /* Сохранять неиспользуемые импортированные значения в выводе JavaScript, которые в противном случае были бы удалены. */ + + /* Ограничения взаимодействия */ + "isolatedModules": true, /* Обеспечить, чтобы каждый файл мог быть безопасно транслирован без зависимости от других импортов. */ + // "verbatimModuleSyntax": true, /* Не преобразовывать или опускать любые импорты или экспорты, не помеченные как только для типов, обеспечивая их запись в формате выходного файла на основе настройки 'module'. */ + // "allowSyntheticDefaultImports": true, /* Разрешить 'import x from y' когда модуль не имеет экспорта по умолчанию. */ + "esModuleInterop": true, /* Генерировать дополнительный JavaScript для упрощения поддержки импорта модулей CommonJS. Это включает 'allowSyntheticDefaultImports' для совместимости типов. */ + // "preserveSymlinks": true, /* Отключить разрешение символических ссылок к их реальному пути. Соответствует тому же флагу в node. */ + "forceConsistentCasingInFileNames": true, /* Обеспечить правильный регистр в импортах. */ + + /* Проверка типов */ + "strict": false, /* Включить все строгие опции проверки типов. */ + "noImplicitAny": false, /* Включить сообщения об ошибках для выражений и объявлений с подразумеваемым типом 'any'. */ + // "strictNullChecks": true, /* При проверке типов учитывать 'null' и 'undefined'. */ + // "strictFunctionTypes": true, /* При присваивании функций проверять, чтобы параметры и возвращаемые значения были совместимы по подтипам. */ + // "strictBindCallApply": true, /* Проверять, что аргументы для методов 'bind', 'call' и 'apply' соответствуют исходной функции. */ + // "strictPropertyInitialization": true, /* Проверять свойства классов, которые объявлены, но не установлены в конструкторе. */ + // "noImplicitThis": true, /* Включить сообщения об ошибках, когда 'this' получает тип 'any'. */ + // "useUnknownInCatchVariables": true, /* Переменные предложения catch по умолчанию как 'unknown' вместо 'any'. */ + // "alwaysStrict": true, /* Обеспечить, чтобы 'use strict' всегда генерировался. */ + // "noUnusedLocals": true, /* Включить сообщения об ошибках, когда локальные переменные не читаются. */ + // "noUnusedParameters": true, /* Вызывать ошибку, когда параметр функции не читается. */ + // "exactOptionalPropertyTypes": true, /* Интерпретировать типы необязательных свойств как написано, а не добавлять 'undefined'. */ + // "noImplicitReturns": true, /* Включить сообщения об ошибках для путей кода, которые не возвращают явно в функции. */ + // "noFallthroughCasesInSwitch": true, /* Включить сообщения об ошибках для случаев провала в операторах switch. */ + // "noUncheckedIndexedAccess": true, /* Добавлять 'undefined' к типу при доступе с использованием индекса. */ + // "noImplicitOverride": true, /* Обеспечить, чтобы переопределяющие члены в производных классах были помечены модификатором override. */ + // "noPropertyAccessFromIndexSignature": true, /* Принуждает использовать индексированные аксессоры для ключей, объявленных с использованием индексированного типа. */ + // "allowUnusedLabels": true, /* Отключить сообщения об ошибках для неиспользуемых меток. */ + // "allowUnreachableCode": true, /* Отключить сообщения об ошибках для недостижимого кода. */ + /* Полнота */ + // "skipDefaultLibCheck": true, /* Пропускать проверку типов .d.ts файлов, которые включены в TypeScript. */ + "skipLibCheck": true /* Пропускать проверку типов всех .d.ts файлов. */ + }, + "exclude": [ + "node_modules", + "legacy/**/*.ts" + ] +}