diff --git a/.commitlintrc.mjs b/.commitlintrc.mjs new file mode 100644 index 0000000..803dc4f --- /dev/null +++ b/.commitlintrc.mjs @@ -0,0 +1,3 @@ +export default { + extends: ['@commitlint/config-conventional'], +}; diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dbafb72 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,41 @@ +# Dependencies +node_modules +npm-debug.log +yarn-error.log +pnpm-debug.log + +# Build output +dist +artifacts +out + +# Environment variables +.env +.env.production +.env.local +!.env.example + +# Docker / Infrastructure +docker-compose.yml +docker-compose.*.yml +Dockerfile +Dockerfile.* +.dockerignore + +# Git +.git +.gitignore +.gitattributes + +# Editor / OS +.vscode +.idea +.DS_Store +*.swp +*.log + +# Tests and Coverage +coverage +test-results +*.spec.ts +*.e2e-spec.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1e287c3 --- /dev/null +++ b/.env.example @@ -0,0 +1,47 @@ +# --- APP --- +PORT=3000 +NODE_ENV=development +COOKIE_SECRET=same-serious-secret +CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 + +# --- POSTGRES --- +DB_SCHEMA=base + +# ВАЖНО: +# Для работы ВНУТРИ Docker: используй хост 'database' и порт 5432 +# DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@database:5432/${DB_DATABASE} + +# Для работы ЛОКАЛЬНО (без докера): используй 'localhost' и порт 6000 +DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_DATABASE} + +# --- REDIS --- +# in the docker network will be, not show port redis, at prod env +# REDIS_HOST=redis +# at development mode +REDIS_HOST=127.0.0.1 +REDIS_PORT=7000 +REDIS_PASSWORD=same-password + +JWT_AUDIENCE="task-tracker-client" + +JWT_ACCESS_SECRET=same-same-same-same-same +JWT_ACCESS_EXPIRES_IN=15m + +JWT_REFRESH_SECRET=same-same-same-same-same +JWT_REFRESH_EXPIRES_IN=15m + +# --- MAIL SETTINGS --- +MAIL_HOST=smtp.gmail.com +MAIL_PORT=465 +MAIL_USER=example@gmail.com + +# 16x password +MAIL_PASSWORD=xxxxxxxxyyyyyyyy +MAIL_FROM_NAME="Task Tracker" +MAIL_FROM_EMAIL=example@gmail.com + +S3_BUCKET_NAME='' +S3_ENDPOINT='' +S3_REGION='' +S3_ACCESS_KEY='' +S3_SECRET_KEY='' \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 259de13..14746fb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,25 +1,46 @@ module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: ['plugin:@typescript-eslint/recommended'], + root: true, + env: { + node: true, + }, + globals: { + describe: 'readonly', + it: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + vi: 'readonly', + }, + ignorePatterns: [ + '.eslintrc.js', + '*.config.{js,ts}', + 'migrations', + 'infra', + '.github', + 'dist', + 'node_modules', + ], + rules: { + 'prettier/prettier': 'off', + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + }, }; diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..26f3a9a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,41 @@ +name: 'Bug Report' +description: 'Сообщить об ошибке в работе приложения' +labels: ['bug', 'triage'] +body: + - type: markdown + attributes: + value: | + Спасибо, что решили помочь сделать проект лучше! + - type: input + id: version + attributes: + label: 'Версия приложения' + description: 'Какую версию вы используете? (например, 0.0.1)' + placeholder: '0.0.x' + validations: + required: true + - type: textarea + id: steps + attributes: + label: 'Шаги воспроизведения' + description: 'Как нам увидеть эту ошибку?' + placeholder: | + 1. Запустить docker-compose + 2. Отправить POST запрос на /api/v1/auth... + validations: + required: true + - type: dropdown + id: environment + attributes: + label: 'Окружение' + options: + - Docker + - Local (pnpm) + - Production + validations: + required: true + - type: textarea + id: expected + attributes: + label: 'Ожидаемое поведение' + placeholder: 'Что должно было произойти?' diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c6c9292 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +blank_issues_enabled: false + +contact_links: + - name: '❓ Вопросы по использованию' + url: 'https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=q-a' + about: 'Если вы не уверены, баг это или нет, или вам нужна помощь в настройке — спросите здесь.' + + - name: '💡 Идеи и предложения' + url: 'https://github.com/Task-Tracker-Lab/task-tracker-backend/discussions/new?category=ideas' + about: 'Хотите обсудить новую крутую фичу перед тем, как заводить задачу? Вам сюда.' diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..c90da45 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,18 @@ +name: '🚀 Feature Request' +description: 'Предложить новую идею или улучшение' +labels: ['enhancement'] +body: + - type: textarea + id: problem + attributes: + label: 'Какую проблему мы решаем?' + description: 'Опишите, почему текущего функционала недостаточно.' + validations: + required: true + - type: textarea + id: solution + attributes: + label: 'Ваше предложение' + description: 'Как именно вы видите реализацию этой фичи?' + validations: + required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f1a92a4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,50 @@ +name: Build and Push + +on: + push: + branches: [dev, main, feat/**] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=sha,format=short + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.prod + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..93a1c10 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + pull_request: + branches: [dev, main, 'feat/**'] + push: + branches: [dev, main, 'feat/**'] + +jobs: + quality-check: + name: Lint & Test + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run Lint + run: pnpm run lint + + - name: Type Check + run: pnpm exec tsc --noEmit + + - name: Run Tests + run: pnpm run test diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..129d47e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,33 @@ +name: 'CodeQL' + +on: + push: + branches: [main, dev, feat/**, chore/**, build/**] + pull_request: + branches: [main] + schedule: + - cron: '15 13 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: javascript-typescript + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..95b126c --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,18 @@ +name: release-please + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + release-type: node diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..f812282 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,19 @@ +name: 'Close stale issues and PRs' + +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: 'Эта задача давно не обновлялась. Она будет закрыта через 5 дней, если не появится новой активности.' + stale-pr-message: 'Этот PR замер. Мы закроем его через 5 дней, чтобы не копить очередь, но вы всегда можете переоткрыть его позже.' + days-before-stale: 30 + days-before-close: 5 diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..0a4b97d --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..e02c24e --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm lint-staged \ No newline at end of file diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs new file mode 100644 index 0000000..0ce6883 --- /dev/null +++ b/.lintstagedrc.mjs @@ -0,0 +1,4 @@ +export default { + '*.{ts,js}': ['eslint --fix', 'prettier --write'], + '*.{json,css,md,yaml,yml}': ['prettier --write'], +}; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..c8d0ce7 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +dist +node_modules +pnpm-lock.yaml + +migrations + +*.sql +Dockerfile* +.dockerignore \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index dcb7279..a6643be 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,7 @@ { - "singleQuote": true, - "trailingComma": "all" -} \ No newline at end of file + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 4, + "semi": true +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6aa8cca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,60 @@ +# Contributing Guidelines 🤝 + +Добро пожаловать в проект! Чтобы поддерживать высокое качество кода и чистоту истории коммитов, мы следуем строгим стандартам разработки. + +## 1. Workflow (Работа с ветками) + +Мы используем **Trunk-based** подход. Это означает, что `main` (trunk) — единственный источник истины. + +- **Запрет на прямые пуши:** Пуш напрямую в `main` (или `master/dev`) строго запрещен. Любые изменения вносятся **только через Pull Request (PR)**. +- **Code Review:** Каждый PR должен пройти обязательное код-ревью перед мерджем. +- **Short-lived branches:** Ветки должны быть короткими (1-2 дня). + +**Правила именования веток:** + +- `feat/` — для новых функциональных возможностей. +- `fix/` — для исправления багов. +- `refactor/` — для переписывания кода без изменения логики. +- `chore/` — для технических задач, настройки окружения и зависимостей +- `docs/` — для обновления документации. + +## 2. Commit Message Convention + +Мы используем стандарт **Conventional Commits**. Это позволяет автоматически генерировать логи изменений и поддерживать историю в чистоте. + +**Формат:** `(): ` + +- **Пример:** `feat(database): add user entity and drizzle migrations` +- **Пример:** `fix(auth): resolve jwt expiration issue` + +## 3. Разработка и стандарты кода + +- **Validation (Zod):** Использование схем **Zod** для эндпоинтов (DTO) обязательно. Без них не будет работать валидация данных и автоматическая типизация в Swagger. +- **Linting & Formatting:** Перед каждым коммитом обязательно запускайте `pnpm lint` и `pnpm format`. Код, не прошедший проверку линтером, не будет принят. +- **Drizzle Migrations:** **Никаких ручных изменений в базе данных.** Все изменения схем должны сопровождаться миграциями. + - Команда: `pnpm db:generate` +- **No God-commits:** Разделяйте свои изменения на небольшие логические коммиты. + +## 4. Pull Request (PR) Process + +Прежде чем отправить PR, убедитесь, что: + +1. **Self-Review:** Вы сами перечитали свой код и удалили отладочные логи (`console.log`). +2. **Checks:** Все автоматические тесты (`pnpm test`) и линтер проходят успешно. +3. **Description:** В описании PR четко указано, что именно было сделано и зачем. + +_PR не принимается, если тесты или линтер упали на этапе CI._ + +## 5. Local Setup + +Для быстрой настройки локального окружения обратитесь к разделу **Quick Start** в [README.md](./README.md). + +Краткий список команд для старта: + +```bash +pnpm install +cp .env.example .env +pnpm db:generate +pnpm db:migrate +pnpm start:dev +``` diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..55b2adf --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,14 @@ +FROM node:23-alpine + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml ./ +COPY tsconfig* ./ + +RUN pnpm install --no-frozen-lockfile + +COPY . . + +CMD ["pnpm", "run", "start:dev"] \ No newline at end of file diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..c68977f --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,53 @@ +FROM node:20-alpine AS base + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" + +RUN corepack enable + +WORKDIR /app + +FROM base AS fetch + +COPY pnpm-lock.yaml ./ + +# Загружаем всё в виртуальное хранилище. +# Если lock-файл не менялся, этот слой будет взят из кэша +RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ + pnpm fetch + +FROM fetch AS build +COPY package.json ./ + +RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ + pnpm install --frozen-lockfile --offline + +COPY . . + +RUN pnpm run build + +RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ + pnpm prune --prod --ignore-scripts + +FROM node:20-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production +ENV PORT=${PORT:-1010} + +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nestjs + +COPY --from=build --chown=nestjs:nodejs /app/dist ./dist +COPY --from=build --chown=nestjs:nodejs /app/node_modules ./node_modules +COPY --from=build --chown=nestjs:nodejs /app/templates ./templates +COPY --from=build --chown=nestjs:nodejs /app/package.json ./ +COPY --from=build --chown=nestjs:nodejs /app/migrations ./migrations +COPY --from=build --chown=nestjs:nodejs /app/drizzle.config.ts ./drizzle.config.ts + +USER nestjs + +EXPOSE $PORT + +CMD ["node", "dist/main"] diff --git a/README.md b/README.md index 6d83c08..cd789c0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ -# task-tracker-backend -Task-tracker-backend +# Task Tracker Backend 🚀 + +Современная лёгкая open-source система управления IT-проектами (альтернатива Jira/Yandex Tracker). Бэкенд построен на высокопроизводительном стеке с упором на типизацию и скорость разработки. + +**Статус:** `In Development` + +## Технологический стек + +- **Runtime:** Node.js 22+ (pnpm) +- **Framework:** NestJS 11 (**Fastify**) +- **Database:** PostgreSQL + **Drizzle ORM** +- **Validation:** Zod +- **API:** Swagger (OpenAPI) +- **Infrastructure:** Docker (Multi-stage builds) +- **Testing:** Vitest + +## Quick Start + +### 1. Окружение + +Скопируйте пример файла окружения и настройте переменные (БД, API ключи DeepSeek): + +```bash +cp .env.example .env +``` + +### 2. Запуск через Docker (Рекомендуется) + +Проект полностью контейнеризирован: + +```bash +docker-compose up --build +``` + +### 3. Локальный запуск + +Если вы хотите запустить проект без Docker: + +```bash +pnpm install +pnpm db:generate +pnpm db:migrate +pnpm start:dev +``` + +## API Documentation + +После запуска проекта документация доступна по адресу: + +**http://localhost:3000/api/v1/docs** + +## Infrastructure + +- CI/CD: Настроены GitHub Actions для автоматической проверки типов, линтинга и запуска тестов. +- Docker: Используются оптимизированные multi-stage образы для минимизации размера production-билда. diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..247cdcc --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + schema: './src/shared/entities/index.ts', + out: './migrations', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL!, + }, +}); diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..57e9bf6 --- /dev/null +++ b/infra/README.md @@ -0,0 +1,7 @@ +# Command to run infra at dev mode + +Run it by pwd at root! Not include at this dir + +```sh +docker compose -f ./infra/dev/compose.dev.yaml --env-file .env --profile infra up --build -d -V +``` diff --git a/infra/dev/README.md b/infra/dev/README.md new file mode 100644 index 0000000..ac46b3a --- /dev/null +++ b/infra/dev/README.md @@ -0,0 +1,41 @@ +# Файл для фронт разрабов + +## Описание + +Данный конфиг разворачивает полный инстанс бэкенда (API + DB + Redis) +для локальной разработки фронтенда. + +## ТРЕБОВАНИЯ: + +1. Положить актуальный файл .env в директорию с этим файлом + (путь: ./infra/dev/.env). +2. Наличие Docker Desktop / Docker Engine. + +## ЗАПУСК: + +Выполните команду из корня проекта: + +```sh +docker compose -f ./infra/dev/compose.dev.yaml --profile infra up --pull always --build -d -V +``` + +## ЧТО ВНУТРИ: + +- API: http://localhost:3000 +- Postgres: localhost:6000 (пароли и база берутся из .env) +- Redis: localhost:7000 + +## ОСОБЕННОСТИ: + +- Авто-миграции: Приложение само накатит SQL-схему при старте. +- Healthchecks: Контейнер API не поднимется, пока DB и Redis + не станут доступны (status: healthy). +- Изоляция: Используется выделенная сеть 'task-tracker-gateway'. + +## RESET: + +Если нужно полностью очистить базу и начать с нуля: + +```sh +docker compose -f ./infra/dev/compose.dev.yaml --profile infra down -v +``` diff --git a/infra/dev/compose.dev.yaml b/infra/dev/compose.dev.yaml new file mode 100644 index 0000000..50ce996 --- /dev/null +++ b/infra/dev/compose.dev.yaml @@ -0,0 +1,118 @@ +version: '3.9' + +name: task-tracker-api + +services: + api: + hostname: api + container_name: api + image: ghcr.io/task-tracker-lab/task-tracker-backend:dev + env_file: + - .env + ports: + - '3000:3000' + depends_on: + database: + condition: service_healthy + redis: + condition: service_healthy + networks: + - backend + deploy: + resources: + limits: + cpus: '2.0' + memory: 1024M + reservations: + cpus: '0.5' + memory: 256M + + database: + hostname: database + container_name: database + image: postgres:16-alpine + restart: always + env_file: + - .env + environment: + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_DATABASE} + ports: + - '6000:5432' + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - backend + healthcheck: + test: [ + 'CMD-SHELL', + 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -q + || exit 1', + ] + interval: 5s + timeout: 5s + retries: 5 + profiles: ['infra'] + + redis: + hostname: redis + container_name: redis + image: redis:7-alpine + restart: always + ports: + - '${REDIS_PORT:-6999}:6379' + command: redis-server --save 60 1 --loglevel notice + volumes: + - redis_data:/data + networks: + - backend + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 5s + timeout: 3s + retries: 5 + profiles: ['infra'] + + minio: + hostname: minio + container_name: minio + image: minio/minio:latest + restart: always + environment: + MINIO_ROOT_USER: ${S3_ACCESS_KEY} + MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY} + ports: + - '9000:9000' # API + - '9001:9001' # Console (UI) + command: server /data --console-address ":9001" + volumes: + - minio_data:/data + networks: + - backend + profiles: ['infra'] + + minio-init: + image: minio/mc:latest + depends_on: + - minio + environment: + MINIO_ROOT_USER: ${S3_ACCESS_KEY} + MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY} + networks: + - backend + profiles: ['infra'] + entrypoint: > + /bin/sh -c " sleep 5; mc alias set myminio http://minio:9000 + ${S3_ACCESS_KEY} ${S3_SECRET_KEY}; mc mb myminio/${S3_BUCKET_NAME} + --ignore-existing; mc anonymous set download + myminio/${S3_BUCKET_NAME}; exit 0; " + +volumes: + postgres_data: + redis_data: + minio_data: + +networks: + backend: + name: task-tracker-gateway diff --git a/libs/bootstrap/src/bootstrap.ts b/libs/bootstrap/src/bootstrap.ts new file mode 100644 index 0000000..93e09cc --- /dev/null +++ b/libs/bootstrap/src/bootstrap.ts @@ -0,0 +1,128 @@ +import { Logger, VersioningType } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { NestFactory } from '@nestjs/core'; +import { setupThrottler } from './setups/throttler'; +import { DEFAULT_THROTTLER_OPTIONS } from './configs/throttler'; +import { setupCors, setupSwagger } from './setups'; +import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify'; +import type { BootstrapOptions } from './interfaces/options.interface'; +import fastifyCookie from '@fastify/cookie'; +import fastifyCompress from '@fastify/compress'; +import fastifyMultipart from '@fastify/multipart'; +import fastifyCsrf from '@fastify/csrf-protection'; +import { createId } from '@paralleldrive/cuid2'; + +export async function bootstrapApp(options: BootstrapOptions) { + const startTime = performance.now(); + const adapter = new FastifyAdapter({ + requestIdHeader: 'x-request-id', + genReqId: (req) => { + return (req.headers['x-request-id'] as string) || createId(); + }, + }); + + const { + appModule, + apiPrefix, + version = 'v1', + serviceName = 'App', + portEnvKey = 'PORT', + defaultPort = 3000, + setupApp, + useCookieParser = true, + useCors = true, + throttlerOptions = DEFAULT_THROTTLER_OPTIONS, + swaggerOptions, + } = options; + + let rootModule = appModule; + + if (throttlerOptions) { + rootModule = setupThrottler(rootModule, throttlerOptions); + } + + const app = await NestFactory.create(rootModule, adapter, { + rawBody: true, + }); + const logger = new Logger(serviceName[0].toUpperCase() + serviceName.slice(1)); + const configService = app.get(ConfigService); + const port = configService.getOrThrow(portEnvKey, defaultPort); + const origins = configService.getOrThrow('CORS_ALLOWED_ORIGINS'); + + app.enableShutdownHooks(); + + await app.register(fastifyCompress, { + global: true, + threshold: 1024, + }); + + await app.register(fastifyMultipart, { + limits: { + fileSize: 5 * 1024 * 1024, + }, + }); + + if (apiPrefix) app.setGlobalPrefix(apiPrefix); + if (version) { + const hasV = version.startsWith('v'); + + app.enableVersioning({ + type: VersioningType.URI, + prefix: hasV ? 'v' : '', + defaultVersion: hasV ? version.slice(1) : version, + }); + } + if (useCors) setupCors(app, origins); + if (swaggerOptions) { + const { path = 'docs', ...metadata } = swaggerOptions; + + const domain = configService.get('DOMAIN'); + const stage = configService.get('STAGE_DOMAIN'); + + const fullOptions = { + ...metadata, + path, + server: { + port, + domain, + stage, + }, + }; + + await setupSwagger(app, fullOptions); + } + if (useCookieParser) { + const secret = configService.getOrThrow('COOKIE_SECRET'); + await app.register(fastifyCookie, { secret }); + await app.register(fastifyCsrf, { + cookieOpts: { + signed: true, + httpOnly: true, + sameSite: 'strict', + secure: configService.getOrThrow('NODE_ENV') === 'production', + }, + }); + } + if (setupApp) setupApp(app); + + await app.listen(port, '0.0.0.0', (_err, address) => { + const prefix = [apiPrefix, version].filter(Boolean).join('/'); + const baseUrl = `${address}${prefix ? '/' + prefix : ''}`; + + const swaggerBase = `${address}${apiPrefix ? '/' + apiPrefix : ''}`; + const swaggerPath = swaggerOptions?.path ?? 'docs'; + + if (_err) { + logger.error(_err); + process.exit(1); + } + + const startupTime = (performance.now() - startTime).toFixed(2); + logger.verbose(`Environment: ${process.env.NODE_ENV || 'development'}`); + logger.verbose(`API Endpoint: ${baseUrl}`); + logger.verbose(`Health Check: ${baseUrl}/health`); + logger.verbose(`Swagger UI: ${swaggerBase}/${swaggerPath}`); + logger.verbose(`OpenAPI (Specs): ${swaggerBase}/${swaggerPath}/s/{json,yaml}`); + logger.verbose(`Boot Time: ${startupTime}ms`); + }); +} diff --git a/libs/bootstrap/src/configs/swagger.ts b/libs/bootstrap/src/configs/swagger.ts new file mode 100644 index 0000000..a13eb68 --- /dev/null +++ b/libs/bootstrap/src/configs/swagger.ts @@ -0,0 +1,7 @@ +import type { SwaggerOptions } from '../interfaces/options.interface'; + +export const SWAGGER_DEFAULTS: SwaggerOptions = { + title: 'API', + description: 'API Documentation', + version: '1.0.0', +}; diff --git a/libs/bootstrap/src/configs/throttler.ts b/libs/bootstrap/src/configs/throttler.ts new file mode 100644 index 0000000..08f8cbe --- /dev/null +++ b/libs/bootstrap/src/configs/throttler.ts @@ -0,0 +1,9 @@ +import type { ThrottlerModuleOptions } from '@nestjs/throttler'; + +export const DEFAULT_THROTTLER_OPTIONS: ThrottlerModuleOptions = [ + { + ttl: 60000, + limit: 100, + skipIf: (context) => context.getType() !== 'http', + }, +]; diff --git a/libs/bootstrap/src/index.ts b/libs/bootstrap/src/index.ts new file mode 100644 index 0000000..cbc96b3 --- /dev/null +++ b/libs/bootstrap/src/index.ts @@ -0,0 +1 @@ +export { bootstrapApp } from './bootstrap'; diff --git a/libs/bootstrap/src/interfaces/index.ts b/libs/bootstrap/src/interfaces/index.ts new file mode 100644 index 0000000..4d45ac8 --- /dev/null +++ b/libs/bootstrap/src/interfaces/index.ts @@ -0,0 +1 @@ +export type { BootstrapOptions, SwaggerOptions } from './options.interface'; diff --git a/libs/bootstrap/src/interfaces/options.interface.ts b/libs/bootstrap/src/interfaces/options.interface.ts new file mode 100644 index 0000000..3d42a76 --- /dev/null +++ b/libs/bootstrap/src/interfaces/options.interface.ts @@ -0,0 +1,36 @@ +import type { Config } from '@libs/config'; +import type { Type } from '@nestjs/common'; +import type { NestFastifyApplication } from '@nestjs/platform-fastify'; +import type { ThrottlerModuleOptions } from '@nestjs/throttler'; + +export interface SwaggerMetadata { + title?: string; + description?: string; + version?: string; + path?: string; +} + +export interface SwaggerInfrastructure { + server?: { + port?: string | number; + domain?: string; + stage?: string; + }; + services?: { name: string; port: number }[]; +} + +export interface SwaggerOptions extends SwaggerMetadata, SwaggerInfrastructure {} + +export interface BootstrapOptions { + apiPrefix?: string; + version?: string; + appModule: Type; + defaultPort?: number; + portEnvKey?: keyof Config; + serviceName: string; + setupApp?: (app: NestFastifyApplication) => Promise | void; + swaggerOptions?: SwaggerMetadata; + throttlerOptions?: ThrottlerModuleOptions; + useCookieParser?: boolean; + useCors?: boolean; +} diff --git a/libs/bootstrap/src/setups/cors.ts b/libs/bootstrap/src/setups/cors.ts new file mode 100644 index 0000000..59a7959 --- /dev/null +++ b/libs/bootstrap/src/setups/cors.ts @@ -0,0 +1,36 @@ +import fastifyCors from '@fastify/cors'; +import type { NestFastifyApplication } from '@nestjs/platform-fastify'; + +export function setupCors(app: NestFastifyApplication, origins: string[]) { + app.getHttpAdapter() + .getInstance() + .register(fastifyCors, { + origin: (origin, callback) => { + // server-to-server / curl / healthcheck + if (!origin) { + return callback(null, true); + } + + try { + const { hostname } = new URL(origin); + const allowedHostnames = origins.map((o) => new URL(o).hostname); + + if ( + allowedHostnames.some( + (allowed) => hostname === allowed || hostname.endsWith(`.${allowed}`), + ) + ) { + return callback(null, origin); + } + + callback(new Error('Not allowed by CORS'), false); + } catch (e) { + callback(new Error('Invalid origin format'), false); + } + }, + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], + preflightContinue: false, + optionsSuccessStatus: 204, + }); +} diff --git a/libs/bootstrap/src/setups/index.ts b/libs/bootstrap/src/setups/index.ts new file mode 100644 index 0000000..2cfe699 --- /dev/null +++ b/libs/bootstrap/src/setups/index.ts @@ -0,0 +1,3 @@ +export { setupCors } from './cors'; +export { setupThrottler } from './throttler'; +export { setupSwagger } from './swagger'; diff --git a/libs/bootstrap/src/setups/swagger.ts b/libs/bootstrap/src/setups/swagger.ts new file mode 100644 index 0000000..b18afe4 --- /dev/null +++ b/libs/bootstrap/src/setups/swagger.ts @@ -0,0 +1,59 @@ +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { cleanupOpenApiDoc } from 'nestjs-zod'; +import type { NestFastifyApplication } from '@nestjs/platform-fastify'; +import type { SwaggerOptions } from '../interfaces'; +import { SWAGGER_DEFAULTS } from '../configs/swagger'; +import { GlobalErrorResponse } from '@shared/error/schema'; + +async function getCustomCSS() { + const rawUrl = 'https://gist.githubusercontent.com/soorq/f745e5c44cfe27aa928048d6d4ccb18a/raw'; + const res = await fetch(rawUrl); + if (!res.ok) { + return ''; + } + return res.text(); +} + +export async function setupSwagger(app: NestFastifyApplication, options: SwaggerOptions = {}) { + const { title, description, version, path, server } = { + ...SWAGGER_DEFAULTS, + ...options, + }; + + const { domain, port, stage } = server || {}; + + const builder = new DocumentBuilder() + .setTitle(title) + .setDescription(description) + .setVersion(version) + .addBearerAuth(); + + if ((!stage || !domain) && port) { + builder.addServer(`http://localhost:${port}`, 'Local'); + } + if (stage) { + builder.addServer(`https://api.${stage}`, 'Staging'); + } + if (domain) { + builder.addServer(`https://api.${domain}`, 'Production'); + } + + const document = SwaggerModule.createDocument(app, builder.build(), { + extraModels: [GlobalErrorResponse.Output], + }); + + const customCss = await getCustomCSS(); + + SwaggerModule.setup(path, app, cleanupOpenApiDoc(document), { + jsonDocumentUrl: `${path}/s/json`, + yamlDocumentUrl: `${path}/s/yaml`, + useGlobalPrefix: true, + ui: true, + customCss, + swaggerOptions: { + persistAuthorization: true, + tagsSorter: 'alpha', + operationsSorter: 'alpha', + }, + }); +} diff --git a/libs/bootstrap/src/setups/throttler.ts b/libs/bootstrap/src/setups/throttler.ts new file mode 100644 index 0000000..29f683b --- /dev/null +++ b/libs/bootstrap/src/setups/throttler.ts @@ -0,0 +1,19 @@ +import { Module, type Type } from '@nestjs/common'; +import type { ThrottlerModuleOptions } from '@nestjs/throttler'; +import { APP_GUARD } from '@nestjs/core'; +import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; + +export function setupThrottler(module: Type, options: ThrottlerModuleOptions) { + @Module({ + imports: [module, ThrottlerModule.forRoot(options)], + providers: [ + { + provide: APP_GUARD, + useClass: ThrottlerGuard, + }, + ], + }) + class RootModule {} + + return RootModule; +} diff --git a/libs/bootstrap/tsconfig.lib.json b/libs/bootstrap/tsconfig.lib.json new file mode 100644 index 0000000..909ede0 --- /dev/null +++ b/libs/bootstrap/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/bootstrap" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/libs/config/src/config.module.ts b/libs/config/src/config.module.ts new file mode 100644 index 0000000..48e55bf --- /dev/null +++ b/libs/config/src/config.module.ts @@ -0,0 +1,45 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule as NestConfigModule } from '@nestjs/config'; +import * as path from 'path'; +import { ConfigSchema } from './config.schema'; +import { ZodError } from 'zod/v4'; + +const validateConfig = (config: Record) => { + try { + return ConfigSchema.parse(config); + } catch (error) { + if (error instanceof ZodError) { + console.group('\nENVIRONMENT_VALIDATION_ERROR\n'); + + error.issues.forEach((issue) => { + const field = issue.path.join('.') || 'ROOT'; + + console.group(`Field: ${field}`); + console.error(`Message: ${issue.message}`); + console.error(`Code: ${issue.code.toUpperCase()}`); + console.groupEnd(); + console.error('\n'); + }); + + console.groupEnd(); + + throw new Error('Invalid environment configuration'); + } + throw error; + } +}; + +@Module({ + imports: [ + NestConfigModule.forRoot({ + isGlobal: true, + envFilePath: path.resolve(process.cwd(), '.env'), + validate: validateConfig, + validationOptions: { + abortEarly: true, + }, + }), + ], + exports: [NestConfigModule], +}) +export class ConfigModule {} diff --git a/libs/config/src/config.schema.ts b/libs/config/src/config.schema.ts new file mode 100644 index 0000000..0c6b30b --- /dev/null +++ b/libs/config/src/config.schema.ts @@ -0,0 +1,94 @@ +import { z } from 'zod/v4'; +import { jwtSecretValidation } from './helpers/jwt-secren-validation'; + +const timeStringSchema = z.string().regex(/^[0-9]+[smhdw]$/, { + message: 'Invalid time format. Use: s, m, h, d, w (e.g., 15m, 24h, 30d)', +}); + +export const ConfigSchema = z.object({ + PORT: z.coerce.number().default(3000), + NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), + COOKIE_SECRET: z.string({ error: 'COOKIE_SECRET is missing' }), + DB_SCHEMA: z.string({ error: 'DB_SCHEMA is missing' }), + DATABASE_URL: z.string().nonempty('DATABASE_URL must be a valid connection string'), + REDIS_HOST: z.string().default('redis'), + REDIS_PORT: z.coerce.number().optional().default(6379), + REDIS_PASSWORD: z.string().optional(), + DOMAIN: z + .string() + .toLowerCase() + .refine((val) => !val || /^[a-z0-9.-]+\.[a-z]{2,}$/.test(val), { + message: 'DOMAIN must be a valid hostname (e.g., example.com)', + }) + .optional(), + STAGE_DOMAIN: z + .string() + .toLowerCase() + .refine((val) => !val || /^[a-z0-9.-]+\.[a-z]{2,}$/.test(val), { + message: 'STAGE_DOMAIN must be a valid hostname', + }) + .optional(), + CORS_ALLOWED_ORIGINS: z + .string() + .min(1, "CORS_ALLOWED_ORIGINS can't be empty") + .transform((val) => val.split(',').map((s) => s.trim())) + .pipe(z.array(z.string().url('Each origin must be a valid URL'))), + JWT_AUDIENCE: z + .string({ + error: 'JWT_AUDIENCE is required', + }) + .min(1), + JWT_ACCESS_SECRET: z.string().refine(jwtSecretValidation, { + message: + 'JWT_ACCESS_SECRET must be at least 32 characters long OR contain at least 5 words separated by hyphens', + }), + JWT_REFRESH_SECRET: z.string().refine(jwtSecretValidation, { + message: + 'JWT_REFRESH_SECRET must be at least 32 characters long OR contain at least 5 words separated by hyphens', + }), + JWT_ACCESS_EXPIRES_IN: timeStringSchema.default('15m'), + JWT_REFRESH_EXPIRES_IN: timeStringSchema.default('30d'), + MAIL_HOST: z + .string({ + error: 'Mail server host (MAIL_HOST) is not specified', + }) + .min(1, 'MAIL_HOST cannot be empty'), + MAIL_PORT: z.coerce.number({ + error: 'Mail port (MAIL_PORT) is not specified', + }), + MAIL_USER: z + .string({ + error: 'Sender email (MAIL_USER) is not specified', + }) + .email('MAIL_USER must be a valid email address'), + MAIL_PASSWORD: z + .string({ + error: 'Mail password (MAIL_PASSWORD) is required', + }) + .min(1, 'Mail password cannot be empty'), + MAIL_FROM_NAME: z + .string({ + error: 'Sender name (MAIL_FROM_NAME) is not specified', + }) + .min(1, 'Sender name cannot be empty'), + MAIL_FROM_EMAIL: z.string().email('Invalid MAIL_FROM_EMAIL format').optional(), + S3_BUCKET_NAME: z + .string({ + error: "S3_BUCKET_NAME is required. Example: 'avatars'", + }) + .min(1), + S3_ENDPOINT: z + .string({ + error: "S3_ENDPOINT is required. Example: 'http://localhost:9000'", + }) + .url('S3_ENDPOINT must be a valid URL'), + S3_REGION: z.string().default('us-east-1'), + S3_ACCESS_KEY: z.string({ + error: 'S3_ACCESS_KEY is missing (MinIO root user or IAM user)', + }), + S3_SECRET_KEY: z.string({ + error: 'S3_SECRET_KEY is missing (MinIO root password or IAM secret)', + }), +}); + +export type Config = z.infer; diff --git a/libs/config/src/config.types.d.ts b/libs/config/src/config.types.d.ts new file mode 100644 index 0000000..c47f988 --- /dev/null +++ b/libs/config/src/config.types.d.ts @@ -0,0 +1,15 @@ +import '@nestjs/config'; +import { Config } from './config.schema'; + +declare module '@nestjs/config' { + interface ConfigService<_K = unknown, _WasValidated extends boolean = false> { + /** + * Переопределяем метод get, чтобы он предлагал ключи из нашей схемы + */ + get(key: T): Config[T]; + /** + * Переопределяем метод getOrThrow, чтобы он предлагал ключи из нашей схемы + */ + getOrThrow(key: T): Config[T]; + } +} diff --git a/libs/config/src/helpers/jwt-secren-validation.ts b/libs/config/src/helpers/jwt-secren-validation.ts new file mode 100644 index 0000000..27a4e18 --- /dev/null +++ b/libs/config/src/helpers/jwt-secren-validation.ts @@ -0,0 +1,7 @@ +export function jwtSecretValidation(val: string) { + const isLongEnough = val.length >= 32; + const words = val.split('-'); + const hasFiveWords = words.length >= 5 && words.every((word) => word.length > 0); + + return isLongEnough || hasFiveWords; +} diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts new file mode 100644 index 0000000..0c71077 --- /dev/null +++ b/libs/config/src/index.ts @@ -0,0 +1,2 @@ +export * from './config.module'; +export * from './config.schema'; diff --git a/libs/config/tsconfig.lib.json b/libs/config/tsconfig.lib.json new file mode 100644 index 0000000..855908d --- /dev/null +++ b/libs/config/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/config" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/libs/database/src/database.constants.ts b/libs/database/src/database.constants.ts new file mode 100644 index 0000000..a6109aa --- /dev/null +++ b/libs/database/src/database.constants.ts @@ -0,0 +1,2 @@ +export const DATABASE_OPTIONS = 'DATABASE_OPTIONS'; +export const DATABASE_SERVICE = 'DATABASE_SERVICE'; diff --git a/libs/database/src/database.module.ts b/libs/database/src/database.module.ts new file mode 100644 index 0000000..07d5c78 --- /dev/null +++ b/libs/database/src/database.module.ts @@ -0,0 +1,149 @@ +import { + type DynamicModule, + Logger, + Module, + OnApplicationShutdown, + type Provider, + type Type, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { Pool } from 'pg'; +import { DATABASE_OPTIONS, DATABASE_SERVICE } from './database.constants'; +import type { + DatabaseModuleAsyncOptions, + DatabaseModuleOptions, + DatabaseModuleOptionsFactory, +} from './interfaces'; + +@Module({ + providers: [], +}) +export class DatabaseModule implements OnApplicationShutdown { + private static logger = new Logger(DatabaseModule.name); + + private static pool: Pool; + + static register(config: DatabaseModuleOptions): DynamicModule { + return { + module: DatabaseModule, + global: config.global ?? false, + providers: [this.createOptionsProvider(config), this.createDatabaseProvider()], + exports: [DATABASE_SERVICE], + }; + } + + static registerAsync(config: DatabaseModuleAsyncOptions): DynamicModule { + return { + module: DatabaseModule, + global: config.global ?? false, + imports: config.imports ?? [], + providers: [...this.createAsyncProviders(config), this.createDatabaseProvider()], + exports: [DATABASE_SERVICE], + }; + } + + private static createOptionsProvider(options: DatabaseModuleOptions): Provider { + return { + provide: DATABASE_OPTIONS, + useValue: options, + }; + } + + private static createDatabaseProvider(): Provider { + return { + provide: DATABASE_SERVICE, + useFactory: async (cfg: ConfigService, opts: DatabaseModuleOptions) => { + const baseUrl = cfg.get('DATABASE_URL'); + const url = new URL(baseUrl); + url.searchParams.set('options', `-c search_path=${opts.schemaName || 'public'}`); + + const pool = new Pool({ + connectionString: url.toString(), + max: 20, + min: 5, + connectionTimeoutMillis: 5000, + idleTimeoutMillis: 30000, + maxUses: 7500, + keepAlive: true, + }); + + this.pool = pool; + + return drizzle(pool, { + schema: opts.schema, + logger: opts.logging + ? { + logQuery(query, params) { + const start = Date.now(); + DatabaseModule.logger.debug(`SQL: ${query}`); + + if (params?.length) { + DatabaseModule.logger.debug( + `Params: ${JSON.stringify(params)}`, + ); + } + + const duration = Date.now() - start; + DatabaseModule.logger.debug(`Execution time: ${duration}ms`); + }, + } + : false, + }); + }, + inject: [ConfigService, DATABASE_OPTIONS], + }; + } + + private static createAsyncProviders(options: DatabaseModuleAsyncOptions): Provider[] { + if (options.useFactory) { + return [ + { + provide: DATABASE_OPTIONS, + useFactory: options.useFactory, + inject: options.inject || [], + }, + ...(options.extraProviders || []), + ]; + } + + const providers: Provider[] = []; + + const useClass = options.useClass || options.useExisting; + if (!useClass) { + throw new Error( + 'You must provide either useClass, useExisting or useFactory in DatabaseModuleAsyncOptions', + ); + } + + providers.push(this.createAsyncOptionsProvider(useClass)); + + if (options.useClass) { + providers.push({ provide: useClass, useClass }); + } + + if (options.extraProviders) { + providers.push(...options.extraProviders); + } + + return providers; + } + + private static createAsyncOptionsProvider( + useClass: Type, + ): Provider { + return { + provide: DATABASE_OPTIONS, + useFactory: async (optionsFactory: DatabaseModuleOptionsFactory) => + optionsFactory.createDatabaseOptions(), + inject: [useClass], + }; + } + + async onApplicationShutdown(_signal?: string) { + if (DatabaseModule.pool) { + DatabaseModule.logger.log('Closing database connections...'); + await DatabaseModule.pool.end(); + } + } +} diff --git a/libs/database/src/index.ts b/libs/database/src/index.ts new file mode 100644 index 0000000..f6e9f8f --- /dev/null +++ b/libs/database/src/index.ts @@ -0,0 +1,3 @@ +export * from './database.module'; +export * from './database.constants'; +export type { DatabaseService } from './interfaces/database-module.interface'; diff --git a/libs/database/src/interfaces/database-module.interface.ts b/libs/database/src/interfaces/database-module.interface.ts new file mode 100644 index 0000000..7926881 --- /dev/null +++ b/libs/database/src/interfaces/database-module.interface.ts @@ -0,0 +1,29 @@ +import type { FactoryProvider, ModuleMetadata, Provider, Type } from '@nestjs/common'; +import type { NodePgDatabase } from 'drizzle-orm/node-postgres'; + +export interface DatabaseModuleOptions { + schemaName: string; + schema: Record; + logging?: boolean; + global?: boolean; +} + +export interface DatabaseModuleOptionsFactory { + createDatabaseOptions(): Promise | DatabaseModuleOptions; +} + +export interface DatabaseModuleAsyncOptions extends Pick< + ModuleMetadata, + 'imports' +> { + useExisting?: Type; + useClass?: Type; + useFactory?: ( + ...args: TArgs + ) => Promise> | Omit; + inject?: FactoryProvider['inject']; + global?: boolean; + extraProviders?: Provider[]; +} + +export type DatabaseService> = NodePgDatabase; diff --git a/libs/database/src/interfaces/index.ts b/libs/database/src/interfaces/index.ts new file mode 100644 index 0000000..9d0a95e --- /dev/null +++ b/libs/database/src/interfaces/index.ts @@ -0,0 +1 @@ +export * from './database-module.interface'; diff --git a/libs/database/tsconfig.lib.json b/libs/database/tsconfig.lib.json new file mode 100644 index 0000000..0add6d4 --- /dev/null +++ b/libs/database/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/database" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/libs/health/src/controller/health.controller.ts b/libs/health/src/controller/health.controller.ts new file mode 100644 index 0000000..e29e304 --- /dev/null +++ b/libs/health/src/controller/health.controller.ts @@ -0,0 +1,50 @@ +import { Controller, Get, HttpStatus, Inject, Logger } from '@nestjs/common'; +import { SkipThrottle } from '@nestjs/throttler'; +import { HealthService } from '../health.service'; +import { GetHealthSwagger, GetPingSwagger } from './health.swagger'; +import { ApiTags } from '@nestjs/swagger'; +import { BaseException } from '@shared/error'; + +@SkipThrottle() +@Controller() +@ApiTags('System') +export class HealthController { + private logger = new Logger(HealthController.name); + + constructor( + private readonly healthService: HealthService, + @Inject('SERVICE_NAME') private readonly serviceName: string, + ) {} + + @Get('health') + @GetHealthSwagger() + async checkHealth() { + const pingData = await this.healthService.getHealthData(); + + if (pingData.status !== 'up') { + this.logger.error(`${this.serviceName} is unhealthy!`); + throw new BaseException( + { + code: 'SERVICE_UNHEALTHY', + message: `Сервис ${this.serviceName} временно недоступен или работает некорректно`, + details: [ + { + target: this.serviceName, + status: pingData.status, + timestamp: new Date().toISOString(), + }, + ], + }, + HttpStatus.SERVICE_UNAVAILABLE, + ); + } + + return 'healthy'; + } + + @Get('ping') + @GetPingSwagger() + async ping() { + return this.healthService.getHealthData(); + } +} diff --git a/libs/health/src/controller/health.controlller.spec.ts b/libs/health/src/controller/health.controlller.spec.ts new file mode 100644 index 0000000..8e061a4 --- /dev/null +++ b/libs/health/src/controller/health.controlller.spec.ts @@ -0,0 +1,47 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { HealthController } from './health.controller'; +import { HttpStatus, Logger } from '@nestjs/common'; + +describe('HealthController', () => { + let controller: HealthController; + let healthServiceMock: { getHealthData: ReturnType }; + const SERVICE_NAME = 'MyService'; + beforeEach(() => { + healthServiceMock = { + getHealthData: vi.fn(), + }; + controller = new HealthController(healthServiceMock as any, SERVICE_NAME); + + vi.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); + }); + + it('should throw SERVICE_UNAVAILABLE when service status is "down"', async () => { + healthServiceMock.getHealthData.mockResolvedValue({ status: 'down' }); + + await expect(controller.checkHealth()).rejects.toMatchObject({ + status: HttpStatus.SERVICE_UNAVAILABLE, + response: { + code: 'SERVICE_UNHEALTHY', + message: expect.stringContaining(SERVICE_NAME), + details: expect.arrayContaining([ + expect.objectContaining({ + status: 'down', + target: SERVICE_NAME, + }), + ]), + }, + }); + }); + + describe('ping', () => { + it('should return the full health payload', async () => { + const mockPayload = { status: 'up' }; + healthServiceMock.getHealthData.mockResolvedValue(mockPayload); + + const result = await controller.ping(); + + expect(result).toEqual(mockPayload); + expect(healthServiceMock.getHealthData).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/libs/health/src/controller/health.swagger.ts b/libs/health/src/controller/health.swagger.ts new file mode 100644 index 0000000..2271969 --- /dev/null +++ b/libs/health/src/controller/health.swagger.ts @@ -0,0 +1,26 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { HealthResponse } from '../dtos'; + +export const GetHealthSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Краткий статус (Health Check)', + description: 'Используется внешними системами для проверки доступности сервиса.', + }), + ApiResponse({ status: 200, description: 'Сервис работает нормально', type: String }), + ApiResponse({ status: 503, description: 'Сервис недоступен или критическая ошибка' }), + ); + +export const GetPingSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Детальный дамп состояния', + description: 'Возвращает аптайм, время старта и метрики памяти.', + }), + ApiResponse({ + status: 200, + description: 'Полная статистика сервиса', + type: HealthResponse.Output, + }), + ); diff --git a/libs/health/src/dtos/health.dto.ts b/libs/health/src/dtos/health.dto.ts new file mode 100644 index 0000000..5ffd93d --- /dev/null +++ b/libs/health/src/dtos/health.dto.ts @@ -0,0 +1,25 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod/v4'; + +const HealthResponseSchema = z.object({ + service: z.string().describe('Название сервиса'), + status: z.enum(['up', 'down']).describe('Текущий статус'), + info: z.object({ + version: z.string().describe('Версия приложения'), + node: z.string().describe('Версия Node.js'), + pid: z.number().describe('ID процесса'), + }), + time: z.object({ + now: z.string().datetime().describe('Текущее время сервера'), + startedAt: z.string().datetime().describe('Время старта сервера'), + uptime: z.string().describe('Аптайм в формате ч/м/с'), + uptimeSeconds: z.number().describe('Аптайм в секундах'), + }), + metrics: z.object({ + rss: z.string().describe('Resident Set Size (общая память)'), + heapUsed: z.string().describe('Использованная память в куче'), + loadAverage: z.string().describe('Средняя нагрузка на CPU'), + }), +}); + +export class HealthResponse extends createZodDto(HealthResponseSchema) {} diff --git a/libs/health/src/dtos/index.ts b/libs/health/src/dtos/index.ts new file mode 100644 index 0000000..718605a --- /dev/null +++ b/libs/health/src/dtos/index.ts @@ -0,0 +1 @@ +export { HealthResponse } from './health.dto'; diff --git a/libs/health/src/health.module.ts b/libs/health/src/health.module.ts new file mode 100644 index 0000000..c391e72 --- /dev/null +++ b/libs/health/src/health.module.ts @@ -0,0 +1,21 @@ +import { type DynamicModule, Global, Module } from '@nestjs/common'; +import { HealthController } from './controller/health.controller'; +import { HealthService } from './health.service'; + +@Global() +@Module({}) +export class HealthModule { + static register(serviceName: string): DynamicModule { + return { + module: HealthModule, + providers: [ + { + provide: 'SERVICE_NAME', + useValue: serviceName, + }, + HealthService, + ], + controllers: [HealthController], + }; + } +} diff --git a/libs/health/src/health.service.ts b/libs/health/src/health.service.ts new file mode 100644 index 0000000..076a1a6 --- /dev/null +++ b/libs/health/src/health.service.ts @@ -0,0 +1,51 @@ +import { Inject, Injectable } from '@nestjs/common'; +import * as os from 'os'; + +@Injectable() +export class HealthService { + private readonly startTime: Date; + + constructor( + @Inject('SERVICE_NAME') + private readonly serviceName: string, + ) { + this.startTime = new Date(); + } + + async getHealthData() { + const uptimeSeconds = Math.floor(process.uptime()); + const mem = process.memoryUsage(); + + return { + service: this.serviceName, + status: 'up', + info: { + version: '1.0.0', + node: process.version, + pid: process.pid, + }, + time: { + now: new Date().toISOString(), + startedAt: this.startTime.toISOString(), + uptime: this.formatUptime(uptimeSeconds), + uptimeSeconds: uptimeSeconds, + }, + metrics: { + rss: this.toMb(mem.rss), + heapUsed: this.toMb(mem.heapUsed), + loadAverage: os.loadavg()[0].toFixed(2), + }, + }; + } + + private toMb(bytes: number) { + return `${Math.round(bytes / 1024 / 1024)}MB`; + } + + private formatUptime(seconds: number) { + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = seconds % 60; + return `${h}h ${m}m ${s}s`; + } +} diff --git a/libs/health/src/index.ts b/libs/health/src/index.ts new file mode 100644 index 0000000..f0f0421 --- /dev/null +++ b/libs/health/src/index.ts @@ -0,0 +1 @@ +export * from './health.module'; diff --git a/libs/health/tsconfig.lib.json b/libs/health/tsconfig.lib.json new file mode 100644 index 0000000..5c0ebaa --- /dev/null +++ b/libs/health/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/health" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/libs/s3/src/index.ts b/libs/s3/src/index.ts new file mode 100644 index 0000000..d819c35 --- /dev/null +++ b/libs/s3/src/index.ts @@ -0,0 +1,2 @@ +export * from './s3.module'; +export * from './s3.service'; diff --git a/libs/s3/src/interfaces/index.ts b/libs/s3/src/interfaces/index.ts new file mode 100644 index 0000000..073bc43 --- /dev/null +++ b/libs/s3/src/interfaces/index.ts @@ -0,0 +1,5 @@ +export type { + S3ModuleOptions, + S3ModuleAsyncOptions, + S3ModuleOptionsFactory, +} from './module.interface'; diff --git a/libs/s3/src/interfaces/module.interface.ts b/libs/s3/src/interfaces/module.interface.ts new file mode 100644 index 0000000..1edd054 --- /dev/null +++ b/libs/s3/src/interfaces/module.interface.ts @@ -0,0 +1,38 @@ +import type { S3ClientConfig } from '@aws-sdk/client-s3'; +import type { FactoryProvider, ModuleMetadata, Provider, Type } from '@nestjs/common'; + +export interface S3ConnectionOptions extends Pick< + S3ClientConfig, + 'credentials' | 'endpoint' | 'region' +> { + bucket: string; +} + +export interface S3OtherOptions extends Omit< + S3ClientConfig, + 'credentials' | 'endpoint' | 'region' +> {} + +export interface S3ModuleOptions { + connection: S3ConnectionOptions; + config?: S3OtherOptions; + global?: boolean; +} + +export interface S3ModuleOptionsFactory { + createS3Options(): Promise | S3ModuleOptions; +} + +export interface S3ModuleAsyncOptions extends Pick< + ModuleMetadata, + 'imports' +> { + useExisting?: Type; + useClass?: Type; + useFactory?: ( + ...args: T + ) => Promise> | Omit; + inject?: FactoryProvider['inject']; + global?: boolean; + extraProviders?: Provider[]; +} diff --git a/libs/s3/src/s3.constants.ts b/libs/s3/src/s3.constants.ts new file mode 100644 index 0000000..c55a9ab --- /dev/null +++ b/libs/s3/src/s3.constants.ts @@ -0,0 +1 @@ +export const S3_OPTIONS = 'S3_OPTIONS'; diff --git a/libs/s3/src/s3.module.ts b/libs/s3/src/s3.module.ts new file mode 100644 index 0000000..2c4b1f2 --- /dev/null +++ b/libs/s3/src/s3.module.ts @@ -0,0 +1,37 @@ +import { type DynamicModule, Module, type Provider } from '@nestjs/common'; +import type { S3ModuleOptions, S3ModuleAsyncOptions } from './interfaces'; +import { S3Service } from './s3.service'; +import { S3_OPTIONS } from './s3.constants'; + +@Module({}) +export class S3Module { + static register(options: S3ModuleOptions): DynamicModule { + const { global, ...config } = options; + + return { + global, + module: S3Module, + providers: [{ provide: S3_OPTIONS, useValue: config }, S3Service], + exports: [S3Service], + }; + } + + static registerAsync(options: S3ModuleAsyncOptions): DynamicModule { + const { imports } = options; + + return { + module: S3Module, + imports: imports || [], + providers: [this.createAsyncOptionsProvider(options), S3Service], + exports: [S3Service], + }; + } + + private static createAsyncOptionsProvider(options: S3ModuleAsyncOptions): Provider { + return { + provide: S3_OPTIONS, + useFactory: options.useFactory, + inject: options.inject || [], + }; + } +} diff --git a/libs/s3/src/s3.service.ts b/libs/s3/src/s3.service.ts new file mode 100644 index 0000000..16b3d3e --- /dev/null +++ b/libs/s3/src/s3.service.ts @@ -0,0 +1,68 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { S3_OPTIONS } from './s3.constants'; +import { S3ModuleOptions } from './interfaces'; +import { PutObjectCommand } from '@aws-sdk/client-s3'; +import { randomUUID } from 'crypto'; +import { extname } from 'path'; + +@Injectable() +export class S3Service { + private readonly s3Client: S3Client; + public readonly bucket: string; + private readonly endpoint: string; + + constructor( + @Inject(S3_OPTIONS) + private readonly options: S3ModuleOptions, + ) { + const { bucket, credentials, endpoint, region } = options.connection; + this.bucket = bucket; + this.endpoint = endpoint as string; + + this.s3Client = new S3Client({ + region, + endpoint, + credentials, + ...options.config, + }); + } + + async deleteFile(fileUrl: string): Promise { + try { + const url = new URL(fileUrl); + const pathParts = url.pathname.split('/'); + const key = pathParts.slice(2).join('/'); + + await this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.bucket, + Key: key, + }), + ); + } catch (error) { + console.error('S3 Rollback failed:', error); + } + } + + async uploadFile( + fileBuffer: Buffer, + originalName: string, + mimetype: string, + folder: string, + ): Promise { + const extension = extname(originalName); + const fileName = `${folder}/${randomUUID()}${extension}`; + + const command = new PutObjectCommand({ + Bucket: this.bucket, + Key: fileName, + Body: fileBuffer, + ContentType: mimetype, + }); + + await this.s3Client.send(command); + + return `${this.endpoint}/${this.bucket}/${fileName}`; + } +} diff --git a/libs/s3/tsconfig.lib.json b/libs/s3/tsconfig.lib.json new file mode 100644 index 0000000..0cd20fa --- /dev/null +++ b/libs/s3/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/s3" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/migrations/0000_stale_sunspot.sql b/migrations/0000_stale_sunspot.sql new file mode 100644 index 0000000..a615183 --- /dev/null +++ b/migrations/0000_stale_sunspot.sql @@ -0,0 +1 @@ +CREATE SCHEMA "base"; diff --git a/migrations/0001_solid_kronos.sql b/migrations/0001_solid_kronos.sql new file mode 100644 index 0000000..faed36a --- /dev/null +++ b/migrations/0001_solid_kronos.sql @@ -0,0 +1,57 @@ +CREATE TABLE "base"."user_activity" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "event_type" varchar(50) NOT NULL, + "entity_id" varchar, + "metadata" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); + +CREATE TABLE "base"."user_notifications" ( + "user_id" text PRIMARY KEY NOT NULL, + "settings" jsonb DEFAULT '{"email":{"task_assigned":true,"mentions":true,"daily_summary":false},"push":{"task_assigned":true,"reminders":true}}'::jsonb NOT NULL +); + +CREATE TABLE "base"."user_security" ( + "user_id" text PRIMARY KEY NOT NULL, + "password_hash" varchar(255) NOT NULL, + "is_2fa_enabled" boolean DEFAULT false NOT NULL, + "two_factor_secret" text, + "last_password_change" timestamp with time zone DEFAULT now() NOT NULL +); + +CREATE TABLE "base"."users" ( + "id" text PRIMARY KEY NOT NULL, + "first_name" varchar(50) NOT NULL, + "last_name" varchar(50) NOT NULL, + "middle_name" varchar(50), + "email" varchar(255) NOT NULL, + "bio" text, + "avatar_url" varchar(512), + "timezone" varchar(50) DEFAULT 'UTC' NOT NULL, + "language" varchar(5) DEFAULT 'ru' NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "users_email_unique" UNIQUE("email") +); + +CREATE TABLE "base"."sessions" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "device_type" varchar(20), + "browser" varchar(50), + "os" varchar(50), + "user_agent" text NOT NULL, + "ip" varchar(45) NOT NULL, + "city" varchar(100), + "country_code" varchar(5), + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "is_revoked" boolean DEFAULT false NOT NULL +); + +ALTER TABLE "base"."user_activity" ADD CONSTRAINT "user_activity_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users"("id") ON DELETE cascade ON UPDATE no action; +ALTER TABLE "base"."user_notifications" ADD CONSTRAINT "user_notifications_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users"("id") ON DELETE cascade ON UPDATE no action; +ALTER TABLE "base"."user_security" ADD CONSTRAINT "user_security_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users"("id") ON DELETE cascade ON UPDATE no action; +ALTER TABLE "base"."sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/migrations/0002_pink_krista_starr.sql b/migrations/0002_pink_krista_starr.sql new file mode 100644 index 0000000..e44a9d8 --- /dev/null +++ b/migrations/0002_pink_krista_starr.sql @@ -0,0 +1,56 @@ +CREATE TYPE "base"."team_role" AS ENUM ('admin', 'moderator', 'member'); + +CREATE TYPE "base"."member_status" AS ENUM ('pending', 'active', 'declined', 'banned'); + +CREATE TABLE + "base"."tags" ( + "id" text PRIMARY KEY NOT NULL, + "name" varchar(50) NOT NULL, + CONSTRAINT "tags_name_unique" UNIQUE ("name") + ); + +CREATE TABLE + "base"."team_members" ( + "team_id" text NOT NULL, + "user_id" text NOT NULL, + "role" "base"."team_role" DEFAULT 'member' NOT NULL, + "status" "base"."member_status" DEFAULT 'pending' NOT NULL, + "joined_at" timestamp, + "created_at" timestamp DEFAULT now () NOT NULL, + CONSTRAINT "team_members_team_id_user_id_pk" PRIMARY KEY ("team_id", "user_id") + ); + +CREATE TABLE + "base"."teams" ( + "id" text PRIMARY KEY NOT NULL, + "slug" varchar(120) NOT NULL, + "name" varchar(100) NOT NULL, + "description" text, + "avatar_url" text, + "cover_url" text, + "owner_id" text, + "created_at" timestamp DEFAULT now () NOT NULL, + "updated_at" timestamp DEFAULT now () NOT NULL, + CONSTRAINT "teams_slug_unique" UNIQUE ("slug") + ); + +CREATE TABLE + "base"."teams_to_tags" ( + "team_id" text NOT NULL, + "tag_id" text NOT NULL, + CONSTRAINT "teams_to_tags_team_id_tag_id_pk" PRIMARY KEY ("team_id", "tag_id") + ); + +ALTER TABLE "base"."team_members" ADD CONSTRAINT "team_members_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "base"."teams" ("id") ON DELETE cascade ON UPDATE no action; + +ALTER TABLE "base"."team_members" ADD CONSTRAINT "team_members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "base"."users" ("id") ON DELETE cascade ON UPDATE no action; + +ALTER TABLE "base"."teams" ADD CONSTRAINT "teams_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "base"."users" ("id") ON DELETE no action ON UPDATE no action; + +ALTER TABLE "base"."teams_to_tags" ADD CONSTRAINT "teams_to_tags_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "base"."teams" ("id") ON DELETE cascade ON UPDATE no action; + +ALTER TABLE "base"."teams_to_tags" ADD CONSTRAINT "teams_to_tags_tag_id_tags_id_fk" FOREIGN KEY ("tag_id") REFERENCES "base"."tags" ("id") ON DELETE cascade ON UPDATE no action; + +CREATE INDEX "member_status_idx" ON "base"."team_members" USING btree ("status"); + +CREATE INDEX "team_slug_idx" ON "base"."teams" USING btree ("slug"); \ No newline at end of file diff --git a/migrations/0003_open_oracle.sql b/migrations/0003_open_oracle.sql new file mode 100644 index 0000000..4fe9269 --- /dev/null +++ b/migrations/0003_open_oracle.sql @@ -0,0 +1,18 @@ +ALTER TYPE "base"."team_role" ADD VALUE 'owner' BEFORE 'admin'; +ALTER TYPE "base"."team_role" ADD VALUE 'lead' BEFORE 'moderator'; +ALTER TYPE "base"."team_role" ADD VALUE 'viewer'; +ALTER TABLE "base"."teams" DROP CONSTRAINT "teams_owner_id_users_id_fk"; + +ALTER TABLE "base"."team_members" ALTER COLUMN "status" SET DATA TYPE text; +ALTER TABLE "base"."team_members" ALTER COLUMN "status" SET DEFAULT 'inactive'::text; +DROP TYPE "base"."member_status"; +CREATE TYPE "base"."member_status" AS ENUM('active', 'banned', 'inactive'); +ALTER TABLE "base"."team_members" ALTER COLUMN "status" SET DEFAULT 'inactive'::"base"."member_status"; +ALTER TABLE "base"."team_members" ALTER COLUMN "status" SET DATA TYPE "base"."member_status" USING "status"::"base"."member_status"; +ALTER TABLE "base"."teams" ADD COLUMN "deleted_at" timestamp; +ALTER TABLE "base"."teams" ADD CONSTRAINT "teams_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "base"."users"("id") ON DELETE set null ON UPDATE no action; +CREATE INDEX "member_role_idx" ON "base"."team_members" USING btree ("user_id","role"); +CREATE UNIQUE INDEX "team_active_slug_idx" ON "base"."teams" USING btree ("slug") WHERE "base"."teams"."deleted_at" is null; +CREATE INDEX "team_owner_idx" ON "base"."teams" USING btree ("owner_id"); +CREATE INDEX "team_deleted_at_idx" ON "base"."teams" USING btree ("deleted_at"); +CREATE INDEX "teams_to_tags_tag_id_idx" ON "base"."teams_to_tags" USING btree ("tag_id"); \ No newline at end of file diff --git a/migrations/0004_chief_talkback.sql b/migrations/0004_chief_talkback.sql new file mode 100644 index 0000000..dc1073f --- /dev/null +++ b/migrations/0004_chief_talkback.sql @@ -0,0 +1,29 @@ +CREATE TYPE "base"."project_status" AS ENUM('active', 'archived', 'template'); +CREATE TYPE "base"."project_visibility" AS ENUM('public', 'private'); +CREATE TABLE "base"."projects" ( + "id" text PRIMARY KEY NOT NULL, + "team_id" text NOT NULL, + "key" varchar(10) NOT NULL, + "name" varchar(100) NOT NULL, + "description" text, + "icon" varchar(255), + "color" varchar(7), + "status" "base"."project_status" DEFAULT 'active' NOT NULL, + "task_sequence" integer DEFAULT 0 NOT NULL, + "owner_id" text, + "visibility" "base"."project_visibility" DEFAULT 'public' NOT NULL, + "is_publicly_viewable" boolean DEFAULT false NOT NULL, + "share_token" varchar(64), + "settings" jsonb DEFAULT '{}'::jsonb, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + "deleted_at" timestamp, + CONSTRAINT "projects_share_token_unique" UNIQUE("share_token") +); + +ALTER TABLE "base"."projects" ADD CONSTRAINT "projects_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "base"."teams"("id") ON DELETE cascade ON UPDATE no action; +ALTER TABLE "base"."projects" ADD CONSTRAINT "projects_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "base"."users"("id") ON DELETE set null ON UPDATE no action; +CREATE UNIQUE INDEX "project_team_key_idx" ON "base"."projects" USING btree ("team_id","key") WHERE "base"."projects"."deleted_at" is null; +CREATE INDEX "project_owner_id_idx" ON "base"."projects" USING btree ("owner_id"); +CREATE INDEX "project_team_id_idx" ON "base"."projects" USING btree ("team_id"); +CREATE INDEX "project_share_token_idx" ON "base"."projects" USING btree ("share_token"); \ No newline at end of file diff --git a/migrations/0005_calm_vivisector.sql b/migrations/0005_calm_vivisector.sql new file mode 100644 index 0000000..2e1e4e9 --- /dev/null +++ b/migrations/0005_calm_vivisector.sql @@ -0,0 +1,33 @@ +CREATE TABLE + "base"."project_shares" ( + "id" text PRIMARY KEY NOT NULL, + "project_id" text NOT NULL, + "token" text NOT NULL, + "expires_at" timestamp + with + time zone, + "created_by" text NOT NULL, + "created_at" timestamp DEFAULT now () NOT NULL, + CONSTRAINT "project_shares_token_unique" UNIQUE ("token") + ); + +ALTER TABLE "base"."projects" +DROP CONSTRAINT "projects_share_token_unique"; + +DROP INDEX "base"."project_share_token_idx"; + +ALTER TABLE "base"."project_shares" ADD CONSTRAINT "project_shares_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "base"."projects" ("id") ON DELETE cascade ON UPDATE no action; + +CREATE INDEX "token_idx" ON "base"."project_shares" USING btree ("token"); + +CREATE INDEX "project_share_project_id_idx" ON "base"."project_shares" USING btree ("project_id"); + +CREATE UNIQUE INDEX "project_team_name_idx" ON "base"."projects" USING btree ("team_id", "name") +WHERE + "base"."projects"."deleted_at" is null; + +ALTER TABLE "base"."projects" +DROP COLUMN "is_publicly_viewable"; + +ALTER TABLE "base"."projects" +DROP COLUMN "share_token"; \ No newline at end of file diff --git a/migrations/meta/0000_snapshot.json b/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..f99863e --- /dev/null +++ b/migrations/meta/0000_snapshot.json @@ -0,0 +1,20 @@ +{ + "id": "a40dfb7f-7d44-4721-bf37-a197b5f1e479", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": {}, + "enums": {}, + "schemas": { + "base": "base" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..3cbcbd8 --- /dev/null +++ b/migrations/meta/0001_snapshot.json @@ -0,0 +1,369 @@ +{ + "id": "c5575cbf-cbee-46d8-af83-95b96a2afceb", + "prevId": "a40dfb7f-7d44-4721-bf37-a197b5f1e479", + "version": "7", + "dialect": "postgresql", + "tables": { + "base.user_activity": { + "name": "user_activity", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_activity_user_id_users_id_fk": { + "name": "user_activity_user_id_users_id_fk", + "tableFrom": "user_activity", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_notifications": { + "name": "user_notifications", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_notifications_user_id_users_id_fk": { + "name": "user_notifications_user_id_users_id_fk", + "tableFrom": "user_notifications", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_security": { + "name": "user_security", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_2fa_enabled": { + "name": "is_2fa_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_password_change": { + "name": "last_password_change", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_security_user_id_users_id_fk": { + "name": "user_security_user_id_users_id_fk", + "tableFrom": "user_security", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.users": { + "name": "users", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "middle_name": { + "name": "middle_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "language": { + "name": "language", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'ru'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.sessions": { + "name": "sessions", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "browser": { + "name": "browser", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "country_code": { + "name": "country_code", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "is_revoked": { + "name": "is_revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": { + "base": "base" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/migrations/meta/0002_snapshot.json b/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..80c77c1 --- /dev/null +++ b/migrations/meta/0002_snapshot.json @@ -0,0 +1,717 @@ +{ + "id": "995af10c-f9b7-416a-b20b-85034dbd20d5", + "prevId": "c5575cbf-cbee-46d8-af83-95b96a2afceb", + "version": "7", + "dialect": "postgresql", + "tables": { + "base.user_activity": { + "name": "user_activity", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_activity_user_id_users_id_fk": { + "name": "user_activity_user_id_users_id_fk", + "tableFrom": "user_activity", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_notifications": { + "name": "user_notifications", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_notifications_user_id_users_id_fk": { + "name": "user_notifications_user_id_users_id_fk", + "tableFrom": "user_notifications", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_security": { + "name": "user_security", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_2fa_enabled": { + "name": "is_2fa_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_password_change": { + "name": "last_password_change", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_security_user_id_users_id_fk": { + "name": "user_security_user_id_users_id_fk", + "tableFrom": "user_security", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.users": { + "name": "users", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "middle_name": { + "name": "middle_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "language": { + "name": "language", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'ru'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.sessions": { + "name": "sessions", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "browser": { + "name": "browser", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "country_code": { + "name": "country_code", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "is_revoked": { + "name": "is_revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.tags": { + "name": "tags", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_name_unique": { + "name": "tags_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.team_members": { + "name": "team_members", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_role", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "member_status", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_status_idx": { + "name": "member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_members_team_id_user_id_pk": { + "name": "team_members_team_id_user_id_pk", + "columns": [ + "team_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams": { + "name": "teams", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_slug_idx": { + "name": "team_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams_to_tags": { + "name": "teams_to_tags", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "teams_to_tags_team_id_teams_id_fk": { + "name": "teams_to_tags_team_id_teams_id_fk", + "tableFrom": "teams_to_tags", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_to_tags_tag_id_tags_id_fk": { + "name": "teams_to_tags_tag_id_tags_id_fk", + "tableFrom": "teams_to_tags", + "tableTo": "tags", + "schemaTo": "base", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "teams_to_tags_team_id_tag_id_pk": { + "name": "teams_to_tags_team_id_tag_id_pk", + "columns": [ + "team_id", + "tag_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "base.team_role": { + "name": "team_role", + "schema": "base", + "values": [ + "admin", + "moderator", + "member" + ] + }, + "base.member_status": { + "name": "member_status", + "schema": "base", + "values": [ + "pending", + "active", + "declined", + "banned" + ] + } + }, + "schemas": { + "base": "base" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/migrations/meta/0003_snapshot.json b/migrations/meta/0003_snapshot.json new file mode 100644 index 0000000..3845d25 --- /dev/null +++ b/migrations/meta/0003_snapshot.json @@ -0,0 +1,808 @@ +{ + "id": "6fbd096d-2d73-46c8-b4f9-a337fb5cb1c2", + "prevId": "995af10c-f9b7-416a-b20b-85034dbd20d5", + "version": "7", + "dialect": "postgresql", + "tables": { + "base.user_activity": { + "name": "user_activity", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_activity_user_id_users_id_fk": { + "name": "user_activity_user_id_users_id_fk", + "tableFrom": "user_activity", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_notifications": { + "name": "user_notifications", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_notifications_user_id_users_id_fk": { + "name": "user_notifications_user_id_users_id_fk", + "tableFrom": "user_notifications", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_security": { + "name": "user_security", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_2fa_enabled": { + "name": "is_2fa_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_password_change": { + "name": "last_password_change", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_security_user_id_users_id_fk": { + "name": "user_security_user_id_users_id_fk", + "tableFrom": "user_security", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.users": { + "name": "users", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "middle_name": { + "name": "middle_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "language": { + "name": "language", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'ru'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.sessions": { + "name": "sessions", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "browser": { + "name": "browser", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "country_code": { + "name": "country_code", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "is_revoked": { + "name": "is_revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.tags": { + "name": "tags", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_name_unique": { + "name": "tags_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.team_members": { + "name": "team_members", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_role", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "member_status", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'inactive'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_status_idx": { + "name": "member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_role_idx": { + "name": "member_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_members_team_id_user_id_pk": { + "name": "team_members_team_id_user_id_pk", + "columns": [ + "team_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams": { + "name": "teams", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_active_slug_idx": { + "name": "team_active_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"base\".\"teams\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_slug_idx": { + "name": "team_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_owner_idx": { + "name": "team_owner_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_deleted_at_idx": { + "name": "team_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams_to_tags": { + "name": "teams_to_tags", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "teams_to_tags_tag_id_idx": { + "name": "teams_to_tags_tag_id_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_to_tags_team_id_teams_id_fk": { + "name": "teams_to_tags_team_id_teams_id_fk", + "tableFrom": "teams_to_tags", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_to_tags_tag_id_tags_id_fk": { + "name": "teams_to_tags_tag_id_tags_id_fk", + "tableFrom": "teams_to_tags", + "tableTo": "tags", + "schemaTo": "base", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "teams_to_tags_team_id_tag_id_pk": { + "name": "teams_to_tags_team_id_tag_id_pk", + "columns": [ + "team_id", + "tag_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "base.team_role": { + "name": "team_role", + "schema": "base", + "values": [ + "owner", + "admin", + "lead", + "moderator", + "member", + "viewer" + ] + }, + "base.member_status": { + "name": "member_status", + "schema": "base", + "values": [ + "active", + "banned", + "inactive" + ] + } + }, + "schemas": { + "base": "base" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/migrations/meta/0004_snapshot.json b/migrations/meta/0004_snapshot.json new file mode 100644 index 0000000..b6f710d --- /dev/null +++ b/migrations/meta/0004_snapshot.json @@ -0,0 +1,1054 @@ +{ + "id": "55316de9-3aec-4333-b5b7-b1b6a78f8ce1", + "prevId": "6fbd096d-2d73-46c8-b4f9-a337fb5cb1c2", + "version": "7", + "dialect": "postgresql", + "tables": { + "base.user_activity": { + "name": "user_activity", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_activity_user_id_users_id_fk": { + "name": "user_activity_user_id_users_id_fk", + "tableFrom": "user_activity", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_notifications": { + "name": "user_notifications", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_notifications_user_id_users_id_fk": { + "name": "user_notifications_user_id_users_id_fk", + "tableFrom": "user_notifications", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_security": { + "name": "user_security", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_2fa_enabled": { + "name": "is_2fa_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_password_change": { + "name": "last_password_change", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_security_user_id_users_id_fk": { + "name": "user_security_user_id_users_id_fk", + "tableFrom": "user_security", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.users": { + "name": "users", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "middle_name": { + "name": "middle_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "language": { + "name": "language", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'ru'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.sessions": { + "name": "sessions", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "browser": { + "name": "browser", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "country_code": { + "name": "country_code", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "is_revoked": { + "name": "is_revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.tags": { + "name": "tags", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_name_unique": { + "name": "tags_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.team_members": { + "name": "team_members", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_role", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "member_status", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'inactive'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_status_idx": { + "name": "member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_role_idx": { + "name": "member_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_members_team_id_user_id_pk": { + "name": "team_members_team_id_user_id_pk", + "columns": [ + "team_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams": { + "name": "teams", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_active_slug_idx": { + "name": "team_active_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"base\".\"teams\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_slug_idx": { + "name": "team_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_owner_idx": { + "name": "team_owner_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_deleted_at_idx": { + "name": "team_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams_to_tags": { + "name": "teams_to_tags", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "teams_to_tags_tag_id_idx": { + "name": "teams_to_tags_tag_id_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_to_tags_team_id_teams_id_fk": { + "name": "teams_to_tags_team_id_teams_id_fk", + "tableFrom": "teams_to_tags", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_to_tags_tag_id_tags_id_fk": { + "name": "teams_to_tags_tag_id_tags_id_fk", + "tableFrom": "teams_to_tags", + "tableTo": "tags", + "schemaTo": "base", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "teams_to_tags_team_id_tag_id_pk": { + "name": "teams_to_tags_team_id_tag_id_pk", + "columns": [ + "team_id", + "tag_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.projects": { + "name": "projects", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "project_status", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "task_sequence": { + "name": "task_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "project_visibility", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "is_publicly_viewable": { + "name": "is_publicly_viewable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "share_token": { + "name": "share_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "project_team_key_idx": { + "name": "project_team_key_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"base\".\"projects\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_owner_id_idx": { + "name": "project_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_team_id_idx": { + "name": "project_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_share_token_idx": { + "name": "project_share_token_idx", + "columns": [ + { + "expression": "share_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_team_id_teams_id_fk": { + "name": "projects_team_id_teams_id_fk", + "tableFrom": "projects", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "projects_owner_id_users_id_fk": { + "name": "projects_owner_id_users_id_fk", + "tableFrom": "projects", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_share_token_unique": { + "name": "projects_share_token_unique", + "nullsNotDistinct": false, + "columns": [ + "share_token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "base.team_role": { + "name": "team_role", + "schema": "base", + "values": [ + "owner", + "admin", + "lead", + "moderator", + "member", + "viewer" + ] + }, + "base.member_status": { + "name": "member_status", + "schema": "base", + "values": [ + "active", + "banned", + "inactive" + ] + }, + "base.project_status": { + "name": "project_status", + "schema": "base", + "values": [ + "active", + "archived", + "template" + ] + }, + "base.project_visibility": { + "name": "project_visibility", + "schema": "base", + "values": [ + "public", + "private" + ] + } + }, + "schemas": { + "base": "base" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/migrations/meta/0005_snapshot.json b/migrations/meta/0005_snapshot.json new file mode 100644 index 0000000..1e3716c --- /dev/null +++ b/migrations/meta/0005_snapshot.json @@ -0,0 +1,1144 @@ +{ + "id": "4cc11042-2c5e-4ffe-bf71-faedea5219e3", + "prevId": "55316de9-3aec-4333-b5b7-b1b6a78f8ce1", + "version": "7", + "dialect": "postgresql", + "tables": { + "base.user_activity": { + "name": "user_activity", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_activity_user_id_users_id_fk": { + "name": "user_activity_user_id_users_id_fk", + "tableFrom": "user_activity", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_notifications": { + "name": "user_notifications", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_notifications_user_id_users_id_fk": { + "name": "user_notifications_user_id_users_id_fk", + "tableFrom": "user_notifications", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_security": { + "name": "user_security", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_2fa_enabled": { + "name": "is_2fa_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_password_change": { + "name": "last_password_change", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_security_user_id_users_id_fk": { + "name": "user_security_user_id_users_id_fk", + "tableFrom": "user_security", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.users": { + "name": "users", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "middle_name": { + "name": "middle_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "language": { + "name": "language", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'ru'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.sessions": { + "name": "sessions", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "browser": { + "name": "browser", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "country_code": { + "name": "country_code", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "is_revoked": { + "name": "is_revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.tags": { + "name": "tags", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_name_unique": { + "name": "tags_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.team_members": { + "name": "team_members", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_role", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "member_status", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'inactive'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_status_idx": { + "name": "member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_role_idx": { + "name": "member_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_members_team_id_user_id_pk": { + "name": "team_members_team_id_user_id_pk", + "columns": [ + "team_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams": { + "name": "teams", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_active_slug_idx": { + "name": "team_active_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"base\".\"teams\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_slug_idx": { + "name": "team_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_owner_idx": { + "name": "team_owner_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_deleted_at_idx": { + "name": "team_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams_to_tags": { + "name": "teams_to_tags", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "teams_to_tags_tag_id_idx": { + "name": "teams_to_tags_tag_id_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_to_tags_team_id_teams_id_fk": { + "name": "teams_to_tags_team_id_teams_id_fk", + "tableFrom": "teams_to_tags", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_to_tags_tag_id_tags_id_fk": { + "name": "teams_to_tags_tag_id_tags_id_fk", + "tableFrom": "teams_to_tags", + "tableTo": "tags", + "schemaTo": "base", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "teams_to_tags_team_id_tag_id_pk": { + "name": "teams_to_tags_team_id_tag_id_pk", + "columns": [ + "team_id", + "tag_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.project_shares": { + "name": "project_shares", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "token_idx": { + "name": "token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_share_project_id_idx": { + "name": "project_share_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_shares_project_id_projects_id_fk": { + "name": "project_shares_project_id_projects_id_fk", + "tableFrom": "project_shares", + "tableTo": "projects", + "schemaTo": "base", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "project_shares_token_unique": { + "name": "project_shares_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.projects": { + "name": "projects", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "project_status", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "task_sequence": { + "name": "task_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "project_visibility", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "project_team_key_idx": { + "name": "project_team_key_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"base\".\"projects\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_team_name_idx": { + "name": "project_team_name_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"base\".\"projects\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_owner_id_idx": { + "name": "project_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_team_id_idx": { + "name": "project_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_team_id_teams_id_fk": { + "name": "projects_team_id_teams_id_fk", + "tableFrom": "projects", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "projects_owner_id_users_id_fk": { + "name": "projects_owner_id_users_id_fk", + "tableFrom": "projects", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "base.team_role": { + "name": "team_role", + "schema": "base", + "values": [ + "owner", + "admin", + "lead", + "moderator", + "member", + "viewer" + ] + }, + "base.member_status": { + "name": "member_status", + "schema": "base", + "values": [ + "active", + "banned", + "inactive" + ] + }, + "base.project_status": { + "name": "project_status", + "schema": "base", + "values": [ + "active", + "archived", + "template" + ] + }, + "base.project_visibility": { + "name": "project_visibility", + "schema": "base", + "values": [ + "public", + "private" + ] + } + }, + "schemas": { + "base": "base" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json new file mode 100644 index 0000000..baeab62 --- /dev/null +++ b/migrations/meta/_journal.json @@ -0,0 +1,48 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1775839169154, + "tag": "0000_stale_sunspot", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1775925642197, + "tag": "0001_solid_kronos", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1776100122085, + "tag": "0002_pink_krista_starr", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1776171079742, + "tag": "0003_open_oracle", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1776262066530, + "tag": "0004_chief_talkback", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1776614072462, + "tag": "0005_calm_vivisector", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/nest-cli.json b/nest-cli.json index f9aa683..572e181 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -1,8 +1,56 @@ { - "$schema": "https://json.schemastore.org/nest-cli", - "collection": "@nestjs/schematics", - "sourceRoot": "src", - "compilerOptions": { - "deleteOutDir": true - } + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "webpack": true + }, + "projects": { + "bootstrap": { + "type": "library", + "root": "libs/bootstrap", + "entryFile": "index", + "sourceRoot": "libs/bootstrap/src", + "compilerOptions": { + "tsConfigPath": "libs/bootstrap/tsconfig.lib.json" + } + }, + "config": { + "type": "library", + "root": "libs/config", + "entryFile": "index", + "sourceRoot": "libs/config/src", + "compilerOptions": { + "tsConfigPath": "libs/config/tsconfig.lib.json" + } + }, + "database": { + "type": "library", + "root": "libs/database", + "entryFile": "index", + "sourceRoot": "libs/database/src", + "compilerOptions": { + "tsConfigPath": "libs/database/tsconfig.lib.json" + } + }, + "health": { + "type": "library", + "root": "libs/health", + "entryFile": "index", + "sourceRoot": "libs/health/src", + "compilerOptions": { + "tsConfigPath": "libs/health/tsconfig.lib.json" + } + }, + "s3": { + "type": "library", + "root": "libs/s3", + "entryFile": "index", + "sourceRoot": "libs/s3/src", + "compilerOptions": { + "tsConfigPath": "libs/s3/tsconfig.lib.json" + } + } + } } diff --git a/package.json b/package.json index 1b859d3..6cea55a 100644 --- a/package.json +++ b/package.json @@ -1,70 +1,93 @@ { - "name": "task-backend", - "version": "0.0.1", - "description": "", - "author": "", - "private": true, - "license": "UNLICENSED", - "scripts": { - "build": "nest build", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "start": "nest start", - "start:dev": "nest start --watch", - "start:debug": "nest start --debug --watch", - "start:prod": "node dist/main", - "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "test": "jest", - "test:watch": "jest --watch", - "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" - }, - "dependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0", - "@nestjs/platform-express": "^10.0.0", - "reflect-metadata": "^0.2.0", - "rxjs": "^7.8.1" - }, - "devDependencies": { - "@nestjs/cli": "^10.0.0", - "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", - "@types/express": "^4.17.17", - "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "@types/supertest": "^6.0.0", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "eslint": "^8.42.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.0", - "jest": "^29.5.0", - "prettier": "^3.0.0", - "source-map-support": "^0.5.21", - "supertest": "^6.3.3", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.3", - "ts-node": "^10.9.1", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" - }, - "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], - "rootDir": "src", - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" + "name": "task-backend", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "MIT", + "scripts": { + "build": "nest build", + "format": "prettier --write \".\"", + "start:prod": "nest start", + "start:dev": "nest start -w", + "start:debug": "nest start -d -w", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "vitest run", + "test:w": "vitest", + "test:c": "vitest run --coverage", + "test:d": "vitest --inspect-brk --inspect --logHeapUsage --threads=false", + "test:e2e": "vitest run -c vitest.config.e2e.ts", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio", + "prepare": "husky" }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], - "coverageDirectory": "../coverage", - "testEnvironment": "node" - }, - "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" + "dependencies": { + "@aws-sdk/client-s3": "^3.1029.0", + "@aws-sdk/s3-request-presigner": "^3.1029.0", + "@bull-board/api": "^6.21.0", + "@bull-board/fastify": "^6.21.0", + "@bull-board/nestjs": "^6.21.0", + "@fastify/compress": "^8.3.1", + "@fastify/cookie": "^11.0.2", + "@fastify/cors": "^11.2.0", + "@fastify/csrf-protection": "^7.1.0", + "@fastify/multipart": "^10.0.0", + "@fastify/static": "^9.1.0", + "@nestjs-modules/ioredis": "^2.2.1", + "@nestjs/bullmq": "^11.0.4", + "@nestjs/common": "^11.1.18", + "@nestjs/config": "^4.0.4", + "@nestjs/core": "^11.1.18", + "@nestjs/jwt": "^11.0.2", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-fastify": "^11.1.18", + "@nestjs/swagger": "^11.2.7", + "@nestjs/throttler": "^6.5.0", + "@paralleldrive/cuid2": "^3.3.0", + "@willsoto/nestjs-prometheus": "^6.1.0", + "argon2": "^0.44.0", + "bullmq": "^5.73.4", + "drizzle-orm": "^0.45.2", + "drizzle-zod": "^0.8.3", + "fastify": "^5.8.4", + "handlebars": "^4.7.9", + "ioredis": "^5.10.1", + "nestjs-zod": "^5.3.0", + "nodemailer": "^8.0.5", + "otplib": "^13.4.0", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "pg": "^8.20.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1", + "transliteration": "^2.6.1", + "ua-parser-js": "^2.0.9", + "zod": "^4.3.6" + }, + "devDependencies": { + "@commitlint/cli": "^20.5.0", + "@commitlint/config-conventional": "^20.5.0", + "@nestjs/cli": "^11.0.19", + "@nestjs/schematics": "^11.0.10", + "@nestjs/testing": "^11.1.18", + "@types/node": "^20.3.1", + "@types/nodemailer": "^8.0.0", + "@types/passport-jwt": "^4.0.1", + "@types/pg": "^8.20.0", + "@types/ua-parser-js": "^0.7.39", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitest/coverage-v8": "^4.1.4", + "drizzle-kit": "^0.31.10", + "eslint": "^8.42.0", + "husky": "^9.1.7", + "lint-staged": "^16.4.0", + "prettier": "^3.0.0", + "ts-loader": "^9.4.3", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3", + "vitest": "^4.1.4" + }, + "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39f752a..3b3126f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,3472 +8,6337 @@ importers: .: dependencies: - '@nestjs/common': + '@aws-sdk/client-s3': + specifier: ^3.1029.0 + version: 3.1029.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.1029.0 + version: 3.1029.0 + '@bull-board/api': + specifier: ^6.21.0 + version: 6.21.0(@bull-board/ui@6.21.0) + '@bull-board/fastify': + specifier: ^6.21.0 + version: 6.21.0 + '@bull-board/nestjs': + specifier: ^6.21.0 + version: 6.21.0(@bull-board/api@6.21.0(@bull-board/ui@6.21.0))(@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@fastify/compress': + specifier: ^8.3.1 + version: 8.3.1 + '@fastify/cookie': + specifier: ^11.0.2 + version: 11.0.2 + '@fastify/cors': + specifier: ^11.2.0 + version: 11.2.0 + '@fastify/csrf-protection': + specifier: ^7.1.0 + version: 7.1.0 + '@fastify/multipart': specifier: ^10.0.0 - version: 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 10.0.0 + '@fastify/static': + specifier: ^9.1.0 + version: 9.1.0 + '@nestjs-modules/ioredis': + specifier: ^2.2.1 + version: 2.2.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(ioredis@5.10.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/bullmq': + specifier: ^11.0.4 + version: 11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.73.4) + '@nestjs/common': + specifier: ^11.1.18 + version: 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^4.0.4 + version: 4.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) '@nestjs/core': - specifier: ^10.0.0 - version: 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/platform-express': - specifier: ^10.0.0 - version: 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22) + specifier: ^11.1.18 + version: 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/jwt': + specifier: ^11.0.2 + version: 11.0.2(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/passport': + specifier: ^11.0.5 + version: 11.0.5(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0) + '@nestjs/platform-fastify': + specifier: ^11.1.18 + version: 11.1.18(@fastify/static@9.1.0)(@fastify/view@11.1.1)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/swagger': + specifier: ^11.2.7 + version: 11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) + '@nestjs/throttler': + specifier: ^6.5.0 + version: 6.5.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) + '@paralleldrive/cuid2': + specifier: ^3.3.0 + version: 3.3.0 + '@willsoto/nestjs-prometheus': + specifier: ^6.1.0 + version: 6.1.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(prom-client@15.1.3) + argon2: + specifier: ^0.44.0 + version: 0.44.0 + bullmq: + specifier: ^5.73.4 + version: 5.73.4 + drizzle-orm: + specifier: ^0.45.2 + version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0) + drizzle-zod: + specifier: ^0.8.3 + version: 0.8.3(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6) + fastify: + specifier: ^5.8.4 + version: 5.8.4 + handlebars: + specifier: ^4.7.9 + version: 4.7.9 + ioredis: + specifier: ^5.10.1 + version: 5.10.1 + nestjs-zod: + specifier: ^5.3.0 + version: 5.3.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6) + nodemailer: + specifier: ^8.0.5 + version: 8.0.5 + otplib: + specifier: ^13.4.0 + version: 13.4.0 + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 + pg: + specifier: ^8.20.0 + version: 8.20.0 reflect-metadata: specifier: ^0.2.0 version: 0.2.2 rxjs: specifier: ^7.8.1 version: 7.8.2 + transliteration: + specifier: ^2.6.1 + version: 2.6.1 + ua-parser-js: + specifier: ^2.0.9 + version: 2.0.9 + zod: + specifier: ^4.3.6 + version: 4.3.6 devDependencies: + '@commitlint/cli': + specifier: ^20.5.0 + version: 20.5.0(@types/node@20.19.39)(conventional-commits-parser@6.4.0)(typescript@5.9.3) + '@commitlint/config-conventional': + specifier: ^20.5.0 + version: 20.5.0 '@nestjs/cli': - specifier: ^10.0.0 - version: 10.4.9 + specifier: ^11.0.19 + version: 11.0.19(@swc/core@1.15.24)(@types/node@20.19.39)(esbuild@0.27.7) '@nestjs/schematics': - specifier: ^10.0.0 - version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3) + specifier: ^11.0.10 + version: 11.0.10(chokidar@4.0.3)(typescript@5.9.3) '@nestjs/testing': - specifier: ^10.0.0 - version: 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22) - '@types/express': - specifier: ^4.17.17 - version: 4.17.25 - '@types/jest': - specifier: ^29.5.2 - version: 29.5.14 + specifier: ^11.1.18 + version: 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)) '@types/node': specifier: ^20.3.1 version: 20.19.39 - '@types/supertest': - specifier: ^6.0.0 - version: 6.0.3 + '@types/nodemailer': + specifier: ^8.0.0 + version: 8.0.0 + '@types/passport-jwt': + specifier: ^4.0.1 + version: 4.0.1 + '@types/pg': + specifier: ^8.20.0 + version: 8.20.0 + '@types/ua-parser-js': + specifier: ^0.7.39 + version: 0.7.39 '@typescript-eslint/eslint-plugin': specifier: ^6.0.0 version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^6.0.0 version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@vitest/coverage-v8': + specifier: ^4.1.4 + version: 4.1.4(vitest@4.1.4) + drizzle-kit: + specifier: ^0.31.10 + version: 0.31.10 eslint: specifier: ^8.42.0 version: 8.57.1 - eslint-config-prettier: - specifier: ^9.0.0 - version: 9.1.2(eslint@8.57.1) - eslint-plugin-prettier: - specifier: ^5.0.0 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.2) - jest: - specifier: ^29.5.0 - version: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) + husky: + specifier: ^9.1.7 + version: 9.1.7 + lint-staged: + specifier: ^16.4.0 + version: 16.4.0 prettier: specifier: ^3.0.0 version: 3.8.2 - source-map-support: - specifier: ^0.5.21 - version: 0.5.21 - supertest: - specifier: ^6.3.3 - version: 6.3.4 - ts-jest: - specifier: ^29.1.0 - version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.4.3 - version: 9.5.7(typescript@5.9.3)(webpack@5.97.1) - ts-node: - specifier: ^10.9.1 - version: 10.9.2(@types/node@20.19.39)(typescript@5.9.3) + version: 9.5.7(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 typescript: specifier: ^5.1.3 version: 5.9.3 + vitest: + specifier: ^4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages: - '@angular-devkit/core@17.3.11': - resolution: {integrity: sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==} - engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/core@19.2.23': + resolution: {integrity: sha512-RazHPQkUEsNU/OZ75w9UeHxGFMthRiuAW2B/uA7eXExBj/1meHrrBfoCA56ujW2GUxVjRtSrMjylKh4R4meiYA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/core@19.2.24': + resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - chokidar: ^3.5.2 + chokidar: ^4.0.0 peerDependenciesMeta: chokidar: optional: true - '@angular-devkit/schematics-cli@17.3.11': - resolution: {integrity: sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==} - engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics-cli@19.2.24': + resolution: {integrity: sha512-bsStZQG67J1HBqTmWxtIcobvgrn32L4UOdL7hGyOru5VxDWPNA8pRnDYavT3hnJeBkJYPoQIw8u7Dm0ecoQprw==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular-devkit/schematics@17.3.11': - resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} - engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics@19.2.23': + resolution: {integrity: sha512-Jzs7YM4X6azmHU7Mw5tQSPMuvaqYS8SLnZOJbtiXCy1JyuW9bm/WBBecNHMiuZ8LHXKhvQ6AVX1tKrzF6uiDmw==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} + '@angular-devkit/schematics@19.2.24': + resolution: {integrity: sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} + '@aws-sdk/client-s3@3.1029.0': + resolution: {integrity: sha512-OuA8RZTxsAaHDcI25j2NGLMaYFI2WpJdDzK3uLmVBmaHwjQKQZOUDVVBcln8pNo3IgkY+HRSJhRR4/xlM//UyQ==} + engines: {node: '>=20.0.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} + '@aws-sdk/core@3.973.27': + resolution: {integrity: sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==} + engines: {node: '>=20.0.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} + '@aws-sdk/crc64-nvme@3.972.6': + resolution: {integrity: sha512-NMbiqKdruhwwgI6nzBVe2jWMkXjaoQz2YOs3rFX+2F3gGyrJDkDPwMpV/RsTFeq2vAQ055wZNtOXFK4NYSkM8g==} + engines: {node: '>=20.0.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} + '@aws-sdk/credential-provider-env@3.972.25': + resolution: {integrity: sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==} + engines: {node: '>=20.0.0'} - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} - engines: {node: '>=6.9.0'} + '@aws-sdk/credential-provider-http@3.972.27': + resolution: {integrity: sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==} + engines: {node: '>=20.0.0'} - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} - engines: {node: '>=6.0.0'} - hasBin: true + '@aws-sdk/credential-provider-ini@3.972.29': + resolution: {integrity: sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/credential-provider-login@3.972.29': + resolution: {integrity: sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/credential-provider-node@3.972.30': + resolution: {integrity: sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/credential-provider-process@3.972.25': + resolution: {integrity: sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/credential-provider-sso@3.972.29': + resolution: {integrity: sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-import-attributes@7.28.6': - resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/credential-provider-web-identity@3.972.29': + resolution: {integrity: sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-bucket-endpoint@3.972.9': + resolution: {integrity: sha512-COToYKgquDyligbcAep7ygs48RK+mwe/IYprq4+TSrVFzNOYmzWvHf6werpnKV5VYpRiwdn+Wa5ZXkPqLVwcTg==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-expect-continue@3.972.9': + resolution: {integrity: sha512-V/FNCjFxnh4VGu+HdSiW4Yg5GELihA1MIDSAdsEPvuayXBVmr0Jaa6jdLAZLH38KYXl/vVjri9DQJWnTAujHEA==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-flexible-checksums@3.974.7': + resolution: {integrity: sha512-uU4/ch2CLHB8Phu1oTKnnQ4e8Ujqi49zEnQYBhWYT53zfFvtJCdGsaOoypBr8Fm/pmCBssRmGoIQ4sixgdLP9w==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-host-header@3.972.9': + resolution: {integrity: sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-location-constraint@3.972.9': + resolution: {integrity: sha512-TyfOi2XNdOZpNKeTJwRUsVAGa+14nkyMb2VVGG+eDgcWG/ed6+NUo72N3hT6QJioxym80NSinErD+LBRF0Ir1w==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-logger@3.972.9': + resolution: {integrity: sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-recursion-detection@3.972.10': + resolution: {integrity: sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-sdk-s3@3.972.28': + resolution: {integrity: sha512-qJHcJQH9UNPUrnPlRtCozKjtqAaypQ5IgQxTNoPsVYIQeuwNIA8Rwt3NvGij1vCDYDfCmZaPLpnJEHlZXeFqmg==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-ssec@3.972.9': + resolution: {integrity: sha512-wSA2BR7L0CyBNDJeSrleIIzC+DzL93YNTdfU0KPGLiocK6YsRv1nPAzPF+BFSdcs0Qa5ku5Kcf4KvQcWwKGenQ==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@aws-sdk/middleware-user-agent@3.972.29': + resolution: {integrity: sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} + '@aws-sdk/nested-clients@3.996.19': + resolution: {integrity: sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.11': + resolution: {integrity: sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1029.0': + resolution: {integrity: sha512-YbHPaha4DYgJWdPorGV5ZSCCqHafGj4GiyqXmXFlCJSsqlOd3xEcemhOZGjrB9epdiVEUtB3DDJXGYYj55ITdQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.16': + resolution: {integrity: sha512-EMdXYB4r/k5RWq86fugjRhid5JA+Z6MpS7n4sij4u5/C+STrkvuf9aFu41rJA9MjUzxCLzv8U2XL8cH2GSRYpQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1026.0': + resolution: {integrity: sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.7': + resolution: {integrity: sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.6': + resolution: {integrity: sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-format-url@3.972.9': + resolution: {integrity: sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.9': + resolution: {integrity: sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==} + + '@aws-sdk/util-user-agent-node@3.973.15': + resolution: {integrity: sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==} + engines: {node: '>=20.0.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.17': + resolution: {integrity: sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==} + engines: {node: '>=20.0.0'} - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@bull-board/api@6.21.0': + resolution: {integrity: sha512-5bX3U8baU4OulDLeXwqWI6/FZolpi1APfoJVXndR4fKdmuYr9cdbH8cg7juublfzX01T+3zoiZkveX7iD5y8gA==} + peerDependencies: + '@bull-board/ui': 6.21.0 + + '@bull-board/fastify@6.21.0': + resolution: {integrity: sha512-2Og70c0Br9fKF6cX5MKLt2WTvGw3yiu+4OG2K8UAE+yFBrm+VNHxEmfvXvsyoVlnT1bzBpLzaxqC21NWCzY6SA==} + + '@bull-board/nestjs@6.21.0': + resolution: {integrity: sha512-h4UhJw9Hc4ehQcs4y+fd7CgSTyIxHN1uFttwWiFuPpMkA+t5/OcAdlB0THigjxwmL2vYgcFzuk9nKb0qHtlRkw==} + peerDependencies: + '@bull-board/api': ^6.21.0 + '@nestjs/bull-shared': ^10.0.0 || ^11.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + rxjs: ^7.8.1 + + '@bull-board/ui@6.21.0': + resolution: {integrity: sha512-SemKRipdrZVqboae/Xhl7CTdIwWJ+F3G/DEP7XHi1Qt1kXZUIKJkySXlFHILunygCiHRpCJ6/Ax/XNdHI/n3QA==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@commitlint/cli@20.5.0': + resolution: {integrity: sha512-yNkyN/tuKTJS3wdVfsZ2tXDM4G4Gi7z+jW54Cki8N8tZqwKBltbIvUUrSbT4hz1bhW/h0CdR+5sCSpXD+wMKaQ==} + engines: {node: '>=v18'} + hasBin: true - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@commitlint/config-conventional@20.5.0': + resolution: {integrity: sha512-t3Ni88rFw1XMa4nZHgOKJ8fIAT9M2j5TnKyTqJzsxea7FUetlNdYFus9dz+MhIRZmc16P0PPyEfh6X2d/qw8SA==} + engines: {node: '>=v18'} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@commitlint/config-validator@20.5.0': + resolution: {integrity: sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==} + engines: {node: '>=v18'} - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@commitlint/ensure@20.5.0': + resolution: {integrity: sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==} + engines: {node: '>=v18'} - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@commitlint/execute-rule@20.0.0': + resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==} + engines: {node: '>=v18'} - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} + '@commitlint/format@20.5.0': + resolution: {integrity: sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==} + engines: {node: '>=v18'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@commitlint/is-ignored@20.5.0': + resolution: {integrity: sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==} + engines: {node: '>=v18'} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} + '@commitlint/lint@20.5.0': + resolution: {integrity: sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==} + engines: {node: '>=v18'} - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} + '@commitlint/load@20.5.0': + resolution: {integrity: sha512-sLhhYTL/KxeOTZjjabKDhwidGZan84XKK1+XFkwDYL/4883kIajcz/dZFAhBJmZPtL8+nBx6bnkzA95YxPeDPw==} + engines: {node: '>=v18'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} + '@commitlint/message@20.4.3': + resolution: {integrity: sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==} + engines: {node: '>=v18'} - '@jest/console@29.7.0': - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@commitlint/parse@20.5.0': + resolution: {integrity: sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==} + engines: {node: '>=v18'} - '@jest/core@29.7.0': - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + '@commitlint/read@20.5.0': + resolution: {integrity: sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==} + engines: {node: '>=v18'} - '@jest/environment@29.7.0': - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@commitlint/resolve-extends@20.5.0': + resolution: {integrity: sha512-3SHPWUW2v0tyspCTcfSsYml0gses92l6TlogwzvM2cbxDgmhSRc+fldDjvGkCXJrjSM87BBaWYTPWwwyASZRrg==} + engines: {node: '>=v18'} - '@jest/expect-utils@29.7.0': - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@commitlint/rules@20.5.0': + resolution: {integrity: sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==} + engines: {node: '>=v18'} - '@jest/expect@29.7.0': - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@commitlint/to-lines@20.0.0': + resolution: {integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==} + engines: {node: '>=v18'} - '@jest/fake-timers@29.7.0': - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@commitlint/top-level@20.4.3': + resolution: {integrity: sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==} + engines: {node: '>=v18'} - '@jest/globals@29.7.0': - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@commitlint/types@20.5.0': + resolution: {integrity: sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==} + engines: {node: '>=v18'} - '@jest/reporters@29.7.0': - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@conventional-changelog/git-client@2.7.0': + resolution: {integrity: sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==} + engines: {node: '>=18'} peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + conventional-commits-filter: ^5.0.0 + conventional-commits-parser: ^6.4.0 peerDependenciesMeta: - node-notifier: + conventional-commits-filter: + optional: true + conventional-commits-parser: optional: true - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} - '@jest/source-map@29.6.3': - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@jest/test-result@29.7.0': - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} - '@jest/test-sequencer@29.7.0': - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] - '@ljharb/through@2.3.14': - resolution: {integrity: sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==} - engines: {node: '>= 0.4'} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] - '@lukeed/csprng@1.1.0': - resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} - engines: {node: '>=8'} + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] - '@nestjs/cli@10.4.9': - resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} - engines: {node: '>= 16.14'} - hasBin: true - peerDependencies: - '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 - '@swc/core': ^1.3.62 - peerDependenciesMeta: - '@swc/cli': - optional: true - '@swc/core': - optional: true + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] - '@nestjs/common@10.4.22': - resolution: {integrity: sha512-fxJ4v85nDHaqT1PmfNCQ37b/jcv2OojtXTaK1P2uAXhzLf9qq6WNUOFvxBrV4fhQek1EQoT1o9oj5xAZmv3NRw==} - peerDependencies: - class-transformer: '*' - class-validator: '*' - reflect-metadata: ^0.1.12 || ^0.2.0 - rxjs: ^7.1.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] - '@nestjs/core@10.4.22': - resolution: {integrity: sha512-6IX9+VwjiKtCjx+mXVPncpkQ5ZjKfmssOZPFexmT+6T9H9wZ3svpYACAo7+9e7Nr9DZSoRZw3pffkJP7Z0UjaA==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/microservices': ^10.0.0 - '@nestjs/platform-express': ^10.0.0 - '@nestjs/websockets': ^10.0.0 - reflect-metadata: ^0.1.12 || ^0.2.0 - rxjs: ^7.1.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - '@nestjs/websockets': - optional: true + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] - '@nestjs/platform-express@10.4.22': - resolution: {integrity: sha512-ySSq7Py/DFozzZdNDH67m/vHoeVdphDniWBnl6q5QVoXldDdrZIHLXLRMPayTDh5A95nt7jjJzmD4qpTbNQ6tA==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] - '@nestjs/schematics@10.2.3': - resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} - peerDependencies: - typescript: '>=4.8.2' + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] - '@nestjs/testing@10.4.22': - resolution: {integrity: sha512-HO9aPus3bAedAC+jKVAA8jTdaj4fs5M9fing4giHrcYV2txe9CvC1l1WAjwQ9RDhEHdugjY4y+FZA/U/YqPZrA==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 - '@nestjs/microservices': ^10.0.0 - '@nestjs/platform-express': ^10.0.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] - '@nuxtjs/opencollective@0.3.2': - resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} - engines: {node: '>=8.0.0', npm: '>=5.0.0'} - hasBin: true + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] - '@paralleldrive/cuid2@2.3.1': - resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] - '@pkgr/core@0.2.9': - resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] - '@sinclair/typebox@0.27.10': - resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] - '@tokenizer/inflate@0.2.7': - resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} + cpu: [arm64] + os: [linux] - '@tokenizer/token@0.3.0': - resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] - '@types/body-parser@1.19.6': - resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] - '@types/cookiejar@2.1.5': - resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] - '@types/eslint-scope@3.7.7': - resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] - '@types/express-serve-static-core@4.19.8': - resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] - '@types/express@4.17.25': - resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] - '@types/http-errors@2.0.5': - resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] - '@types/jest@29.5.14': - resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/methods@1.1.4': - resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} - - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - - '@types/node@20.19.39': - resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} - - '@types/qs@6.15.0': - resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - - '@types/semver@7.7.1': - resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - - '@types/send@0.17.6': - resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} - - '@types/send@1.2.1': - resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} - - '@types/serve-static@1.15.10': - resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} - - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - - '@types/superagent@8.1.9': - resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] - '@types/supertest@6.0.3': - resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] - '@types/yargs@17.0.35': - resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] - '@typescript-eslint/eslint-plugin@6.21.0': - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] - '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] - '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] - '@typescript-eslint/type-utils@6.21.0': - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] - '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] - '@typescript-eslint/utils@6.21.0': - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] - '@webassemblyjs/ast@1.14.1': - resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] - '@webassemblyjs/floating-point-hex-parser@1.13.2': - resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] - '@webassemblyjs/helper-api-error@1.13.2': - resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] - '@webassemblyjs/helper-buffer@1.14.1': - resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] - '@webassemblyjs/helper-numbers@1.13.2': - resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] - '@webassemblyjs/helper-wasm-bytecode@1.13.2': - resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] - '@webassemblyjs/helper-wasm-section@1.14.1': - resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] - '@webassemblyjs/ieee754@1.13.2': - resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] - '@webassemblyjs/leb128@1.13.2': - resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] - '@webassemblyjs/utf8@1.13.2': - resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] - '@webassemblyjs/wasm-edit@1.14.1': - resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] - '@webassemblyjs/wasm-gen@1.14.1': - resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] - '@webassemblyjs/wasm-opt@1.14.1': - resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] - '@webassemblyjs/wasm-parser@1.14.1': - resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@webassemblyjs/wast-printer@1.14.1': - resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} + '@fastify/accept-negotiator@2.0.1': + resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@fastify/ajv-compiler@4.0.5': + resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} - acorn-walk@8.3.5: - resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} - engines: {node: '>=0.4.0'} + '@fastify/busboy@3.2.0': + resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true + '@fastify/compress@8.3.1': + resolution: {integrity: sha512-BUpItLr6MUX9e9ukg5Y6xekyA/7pBFG8QWtFCrUDm9ctoBc3R2/nA16yOaOWtVoccpXGjdDEYA/MxAb5+8cxag==} - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true + '@fastify/cookie@11.0.2': + resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==} - ajv-keywords@3.5.2: - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 + '@fastify/cors@11.2.0': + resolution: {integrity: sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==} - ajv-keywords@5.1.0: - resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} - peerDependencies: - ajv: ^8.8.2 + '@fastify/csrf-protection@7.1.0': + resolution: {integrity: sha512-I2TDd4SRRYQivKCMHdB/8py+CPO9DT0e63lh4DO8MDCJh8NROq8HD/iO0IjYtwhsD3bZhr0cBXsFdfPvyTmzNw==} - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + '@fastify/csrf@8.0.1': + resolution: {integrity: sha512-dAmCrdfJ3CV/A/hHHK/rRBjjLRRSIltgJB0BxiVfbhr/31G6fgF8l2I8evtH8mjS5kTIvd0JOh7MOA3HA6eYDw==} - ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + '@fastify/deepmerge@3.2.1': + resolution: {integrity: sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA==} - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + '@fastify/error@4.2.0': + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} + '@fastify/fast-json-stringify-compiler@5.0.3': + resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + '@fastify/formbody@8.0.2': + resolution: {integrity: sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA==} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + '@fastify/forwarded@3.0.1': + resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} + '@fastify/merge-json-schemas@0.2.1': + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + '@fastify/multipart@10.0.0': + resolution: {integrity: sha512-pUx3Z1QStY7E7kwvDTIvB6P+rF5lzP+iqPgZyJyG3yBJVPvQaZxzDHYbQD89rbY0ciXrMOyGi8ezHDVexLvJDA==} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} + '@fastify/proxy-addr@5.1.0': + resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} + '@fastify/send@4.1.0': + resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + '@fastify/static@9.1.0': + resolution: {integrity: sha512-EPRNQYqEYEYTK8yyGbcM0iHpyJaupb94bey5O6iCQfLTADr02kaZU+qeHSdd9H9TiMwTBVkrMa59V8CMbn3avQ==} - append-field@1.0.0: - resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + '@fastify/view@11.1.1': + resolution: {integrity: sha512-GiHqT3R2eKJgWmy0s45eELTC447a4+lTM2o+8fSWeKwBe9VToeePuHJcKtOEXPrKGSddGO0RsNayULiS3aeHeQ==} - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} - array-timsort@1.0.3: - resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.8.0 + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} - babel-preset-current-node-syntax@1.2.0: - resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0 || ^8.0.0-0 + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} peerDependencies: - '@babel/core': ^7.0.0 + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - baseline-browser-mapping@2.10.17: - resolution: {integrity: sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==} + '@inquirer/prompts@7.3.2': + resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@ioredis/commands@1.5.1': + resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - hasBin: true - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} - body-parser@1.20.4: - resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + '@microsoft/tsdoc@0.16.0': + resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] - brace-expansion@2.0.3: - resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + '@napi-rs/wasm-runtime@1.1.3': + resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + '@nestjs-modules/ioredis@2.2.1': + resolution: {integrity: sha512-wQ08XvlV2s9V+01SKcC5XmFoQ2hMAHP0KuVja8UFZyE/dM0bKI5HSHr+3wQ5ChRpsyhfxF/vKrlPXMlJIr7FIg==} + peerDependencies: + '@nestjs/common': '>=6.7.0' + '@nestjs/core': '>=6.7.0' + ioredis: '>=5.0.0' - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} + '@nestjs/bull-shared@11.0.4': + resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} + '@nestjs/bullmq@11.0.4': + resolution: {integrity: sha512-wBzK9raAVG0/6NTMdvLGM4/FQ1lsB35/pYS8L6a0SDgkTiLpd7mAjQ8R692oMx5s7IjvgntaZOuTUrKYLNfIkA==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + bullmq: ^3.0.0 || ^4.0.0 || ^5.0.0 - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} + '@nestjs/cli@11.0.19': + resolution: {integrity: sha512-9htODqTVVNH4lJqyeIotsAgfeaYngDi020cVCd6JhJRKuOT83c/t4JDSky6+xr0lhHyNTNMgZmulxqcMNZFfrw==} + engines: {node: '>= 20.11'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} - engines: {node: '>= 0.4'} + '@nestjs/common@11.1.18': + resolution: {integrity: sha512-0sLq8Z+TIjLnz1Tqp0C/x9BpLbqpt1qEu0VcH4/fkE0y3F5JxhfK1AdKQ/SPbKhKgwqVDoY4gS8GQr2G6ujaWg==} + peerDependencies: + class-transformer: '>=0.4.1' + class-validator: '>=0.13.2' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} + '@nestjs/config@4.0.4': + resolution: {integrity: sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + '@nestjs/core@11.1.18': + resolution: {integrity: sha512-wR3DtGyk/LUAiPtbXDuWJJwVkWElKBY0sqnTzf9d4uM3+X18FRZhK7WFc47czsIGOdWuRsMeLYV+1Z9dO4zDEQ==} + engines: {node: '>= 20'} + peerDependencies: + '@nestjs/common': ^11.0.0 + '@nestjs/microservices': ^11.0.0 + '@nestjs/platform-express': ^11.0.0 + '@nestjs/websockets': ^11.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} + '@nestjs/jwt@11.0.2': + resolution: {integrity: sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} + '@nestjs/mapped-types@2.1.1': + resolution: {integrity: sha512-SCCoMEJ6jdeI5h/N+KCVF1+pmg/hmEkNA5nHTS8Gvww7T/LCl4o1gFLinw2iQ60w7slFkszHcGLKGdazVI4F8A==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 || ^0.15.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true - caniuse-lite@1.0.30001787: - resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} + '@nestjs/passport@11.0.5': + resolution: {integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + passport: ^0.5.0 || ^0.6.0 || ^0.7.0 - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + '@nestjs/platform-fastify@11.1.18': + resolution: {integrity: sha512-iJtbqQz51k7Z1vOTUEHO1mU8PsDO1WdgPSJ/6CuXBnazkrkePXoszhefFaPwJreBVn35GE3WTd/6ou7bFwnhmA==} + peerDependencies: + '@fastify/static': ^8.0.0 || ^9.0.0 + '@fastify/view': ^10.0.0 || ^11.0.0 + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + '@fastify/view': + optional: true - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + '@nestjs/schematics@11.0.10': + resolution: {integrity: sha512-q9lr0wGwgBHLarD4uno3XiW4JX60WPlg2VTgbqPHl/6bT4u1IEEzj+q9Tad3bVnqL5mlDF3vrZ2tj+x13CJpmw==} + peerDependencies: + typescript: '>=4.8.2' - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} + '@nestjs/swagger@11.2.7': + resolution: {integrity: sha512-+e1KWSyZMAQeyZ8nbQSvm3fhzqdxxBNQENvpjO2dVyD7KJmLTTQyXpRb1nM5O04oFdDTUtG3SHMl4+e+zgCK2A==} + peerDependencies: + '@fastify/static': ^8.0.0 || ^9.0.0 + '@nestjs/common': ^11.0.1 + '@nestjs/core': ^11.0.1 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + '@nestjs/terminus@11.1.1': + resolution: {integrity: sha512-Ssql79H+EQY/Wg108eJqN4NiNsO/tLrj+qbzOWSQUf2JE4vJQ2RG3WTqUOrYjfjWmVHD3+Ys0+azed7LSMKScw==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0 + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/microservices': ^10.0.0 || ^11.0.0 + '@nestjs/mongoose': ^11.0.0 + '@nestjs/sequelize': ^10.0.0 || ^11.0.0 + '@nestjs/typeorm': ^10.0.0 || ^11.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + '@nestjs/testing@11.1.18': + resolution: {integrity: sha512-frzwNlpBgtAzI3hp/qo57DZoRO4RMTH1wST3QUYEhRTHyfPkLpzkWz3jV/mhApXjD0yT56Ptlzn6zuYPLh87Lw==} + peerDependencies: + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 + '@nestjs/microservices': ^11.0.0 + '@nestjs/platform-express': ^11.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true - chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} + '@nestjs/throttler@6.5.0': + resolution: {integrity: sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} + '@noble/hashes@2.0.1': + resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + engines: {node: '>= 20.19.0'} - cjs-module-lexer@1.4.3: - resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} + '@nuxt/opencollective@0.4.1': + resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} + engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} + hasBin: true - cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} + '@otplib/core@13.4.0': + resolution: {integrity: sha512-JqOGcvZQi2wIkEQo8f3/iAjstavpXy6gouIDMHygjNuH6Q0FjbHOiXMdcE94RwfgDNMABhzwUmvaPsxvgm9NYw==} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + '@otplib/hotp@13.4.0': + resolution: {integrity: sha512-MJjE0x06mn2ptymz5qZmQveb+vWFuaIftqE0b5/TZZqUOK7l97cV8lRTmid5BpAQMwJDNLW6RnYxGeCRiNdekw==} - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} + '@otplib/plugin-base32-scure@13.4.0': + resolution: {integrity: sha512-/t9YWJmMbB8bF5z8mXrBZc2FXBe8B/3hG5FhWr9K8cFwFhyxScbPysmZe8s1UTzSA6N+s8Uv8aIfCtVXPNjJWw==} - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + '@otplib/plugin-crypto-noble@13.4.0': + resolution: {integrity: sha512-KrvE4m7Zv+TT1944HzgqFJWJpKb6AyoxDbvhPStmBqdMlv5Gekb80d66cuFRL08kkPgJ5gXUSb5SFpYeB+bACg==} - collect-v8-coverage@1.0.3: - resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + '@otplib/totp@13.4.0': + resolution: {integrity: sha512-dK+vl0f0ekzf6mCENRI9AKS2NJUC7OjI3+X8e7QSnhQ2WM7I+i4PGpb3QxKi5hxjTtwVuoZwXR2CFtXdcRtNdQ==} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + '@otplib/uri@13.4.0': + resolution: {integrity: sha512-x1ozBa5bPbdZCrrTL/HK21qchiK7jYElTu+0ft22abeEhiLYgH1+SIULvOcVk3CK8YwF4kdcidvkq4ciejucJA==} - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + '@oxc-project/types@0.124.0': + resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} + '@paralleldrive/cuid2@3.3.0': + resolution: {integrity: sha512-OqiFvSOF0dBSesELYY2CAMa4YINvlLpvKOz/rv6NeZEqiyttlHgv98Juwv4Ch+GrEV7IZ8jfI2VcEoYUjXXCjw==} + hasBin: true - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + '@phc/format@1.0.0': + resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} + engines: {node: '>=10'} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - comment-json@4.2.5: - resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} - engines: {node: '>= 6'} + '@rolldown/binding-android-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] - component-emitter@1.3.1: - resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] - concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - engines: {'0': node >= 6.0} + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.15': + resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + + '@scure/base@2.0.0': + resolution: {integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==} + + '@simple-libs/child-process-utils@1.0.2': + resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==} + engines: {node: '>=18'} - consola@2.15.3: - resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} + engines: {node: '>=18'} - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} + '@smithy/chunked-blob-reader-native@4.2.3': + resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} + engines: {node: '>=18.0.0'} - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} + '@smithy/chunked-blob-reader@5.2.2': + resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==} + engines: {node: '>=18.0.0'} - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + '@smithy/config-resolver@4.4.14': + resolution: {integrity: sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==} + engines: {node: '>=18.0.0'} - cookie-signature@1.0.7: - resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + '@smithy/core@3.23.14': + resolution: {integrity: sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==} + engines: {node: '>=18.0.0'} - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} + '@smithy/credential-provider-imds@4.2.13': + resolution: {integrity: sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==} + engines: {node: '>=18.0.0'} - cookiejar@2.1.4: - resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + '@smithy/eventstream-codec@4.2.13': + resolution: {integrity: sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==} + engines: {node: '>=18.0.0'} - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + '@smithy/eventstream-serde-browser@4.2.13': + resolution: {integrity: sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==} + engines: {node: '>=18.0.0'} - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} + '@smithy/eventstream-serde-config-resolver@4.3.13': + resolution: {integrity: sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==} + engines: {node: '>=18.0.0'} - cosmiconfig@8.3.6: - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true + '@smithy/eventstream-serde-node@4.2.13': + resolution: {integrity: sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==} + engines: {node: '>=18.0.0'} - create-jest@29.7.0: - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true + '@smithy/eventstream-serde-universal@4.2.13': + resolution: {integrity: sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==} + engines: {node: '>=18.0.0'} - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + '@smithy/fetch-http-handler@5.3.16': + resolution: {integrity: sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==} + engines: {node: '>=18.0.0'} - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + '@smithy/hash-blob-browser@4.2.14': + resolution: {integrity: sha512-rtQ5es8r/5v4rav7q5QTsfx9CtCyzrz/g7ZZZBH2xtMmd6G/KQrLOWfSHTvFOUPlVy59RQvxeBYJaLRoybMEyA==} + engines: {node: '>=18.0.0'} - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + '@smithy/hash-node@4.2.13': + resolution: {integrity: sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==} + engines: {node: '>=18.0.0'} - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + '@smithy/hash-stream-node@4.2.13': + resolution: {integrity: sha512-WdQ7HwUjINXETeh6dqUeob1UHIYx8kAn9PSp1HhM2WWegiZBYVy2WXIs1lB07SZLan/udys9SBnQGt9MQbDpdg==} + engines: {node: '>=18.0.0'} - dedent@1.7.2: - resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true + '@smithy/invalid-dependency@4.2.13': + resolution: {integrity: sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==} + engines: {node: '>=18.0.0'} - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + '@smithy/md5-js@4.2.13': + resolution: {integrity: sha512-cNm7I9NXolFxtS20ojROddOEpSAeI1Obq6pd1Kj5HtHws3s9Fkk8DdHDfQSs5KuxCewZuVK6UqrJnfJmiMzDuQ==} + engines: {node: '>=18.0.0'} - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} + '@smithy/middleware-content-length@4.2.13': + resolution: {integrity: sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==} + engines: {node: '>=18.0.0'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} + '@smithy/middleware-endpoint@4.4.29': + resolution: {integrity: sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==} + engines: {node: '>=18.0.0'} - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} + '@smithy/middleware-retry@4.5.1': + resolution: {integrity: sha512-/zY+Gp7Qj2D2hVm3irkCyONER7E9MiX3cUUm/k2ZmhkzZkrPgwVS4aJ5NriZUEN/M0D1hhjrgjUmX04HhRwdWA==} + engines: {node: '>=18.0.0'} - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + '@smithy/middleware-serde@4.2.17': + resolution: {integrity: sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==} + engines: {node: '>=18.0.0'} - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} + '@smithy/middleware-stack@4.2.13': + resolution: {integrity: sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==} + engines: {node: '>=18.0.0'} - dezalgo@1.0.4: - resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + '@smithy/node-config-provider@4.3.13': + resolution: {integrity: sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==} + engines: {node: '>=18.0.0'} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@smithy/node-http-handler@4.5.2': + resolution: {integrity: sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==} + engines: {node: '>=18.0.0'} - diff@4.0.4: - resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} - engines: {node: '>=0.3.1'} + '@smithy/property-provider@4.2.13': + resolution: {integrity: sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==} + engines: {node: '>=18.0.0'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + '@smithy/protocol-http@5.3.13': + resolution: {integrity: sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==} + engines: {node: '>=18.0.0'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + '@smithy/querystring-builder@4.2.13': + resolution: {integrity: sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==} + engines: {node: '>=18.0.0'} - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} + '@smithy/querystring-parser@4.2.13': + resolution: {integrity: sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==} + engines: {node: '>=18.0.0'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + '@smithy/service-error-classification@4.2.13': + resolution: {integrity: sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==} + engines: {node: '>=18.0.0'} - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + '@smithy/shared-ini-file-loader@4.4.8': + resolution: {integrity: sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==} + engines: {node: '>=18.0.0'} - electron-to-chromium@1.5.334: - resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==} + '@smithy/signature-v4@5.3.13': + resolution: {integrity: sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==} + engines: {node: '>=18.0.0'} - emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} + '@smithy/smithy-client@4.12.9': + resolution: {integrity: sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==} + engines: {node: '>=18.0.0'} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + '@smithy/types@4.14.0': + resolution: {integrity: sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==} + engines: {node: '>=18.0.0'} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + '@smithy/url-parser@4.2.13': + resolution: {integrity: sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==} + engines: {node: '>=18.0.0'} - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} - enhanced-resolve@5.20.1: - resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} - engines: {node: '>=10.13.0'} + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} + '@smithy/util-defaults-mode-browser@4.3.45': + resolution: {integrity: sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==} + engines: {node: '>=18.0.0'} - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} + '@smithy/util-defaults-mode-node@4.2.49': + resolution: {integrity: sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==} + engines: {node: '>=18.0.0'} - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} + '@smithy/util-endpoints@3.3.4': + resolution: {integrity: sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==} + engines: {node: '>=18.0.0'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + '@smithy/util-middleware@4.2.13': + resolution: {integrity: sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==} + engines: {node: '>=18.0.0'} - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} + '@smithy/util-retry@4.3.1': + resolution: {integrity: sha512-FwmicpgWOkP5kZUjN3y+3JIom8NLGqSAJBeoIgK0rIToI817TEBHCrd0A2qGeKQlgDeP+Jzn4i0H/NLAXGy9uQ==} + engines: {node: '>=18.0.0'} - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + '@smithy/util-stream@4.5.22': + resolution: {integrity: sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==} + engines: {node: '>=18.0.0'} - eslint-config-prettier@9.1.2: - resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} - eslint-plugin-prettier@5.5.5: - resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@smithy/util-waiter@4.2.15': + resolution: {integrity: sha512-oUt9o7n8hBv3BL56sLSneL0XeigZSuem0Hr78JaoK33D9oKieyCvVP8eTSe3j7g2mm/S1DvzxKieG7JEWNJUNg==} + engines: {node: '>=18.0.0'} - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@swc/core-darwin-arm64@1.15.24': + resolution: {integrity: sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true + '@swc/core-darwin-x64@1.15.24': + resolution: {integrity: sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} + '@swc/core-linux-arm-gnueabihf@1.15.24': + resolution: {integrity: sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + '@swc/core-linux-arm64-gnu@1.15.24': + resolution: {integrity: sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} + '@swc/core-linux-arm64-musl@1.15.24': + resolution: {integrity: sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + '@swc/core-linux-ppc64-gnu@1.15.24': + resolution: {integrity: sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==} + engines: {node: '>=10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + '@swc/core-linux-s390x-gnu@1.15.24': + resolution: {integrity: sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==} + engines: {node: '>=10'} + cpu: [s390x] + os: [linux] + libc: [glibc] - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} + '@swc/core-linux-x64-gnu@1.15.24': + resolution: {integrity: sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} + '@swc/core-linux-x64-musl@1.15.24': + resolution: {integrity: sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + '@swc/core-win32-arm64-msvc@1.15.24': + resolution: {integrity: sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==} engines: {node: '>=10'} + cpu: [arm64] + os: [win32] - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} + '@swc/core-win32-ia32-msvc@1.15.24': + resolution: {integrity: sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@swc/core-win32-x64-msvc@1.15.24': + resolution: {integrity: sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] - express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} - engines: {node: '>= 0.10.0'} + '@swc/core@1.15.24': + resolution: {integrity: sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + '@swc/types@0.1.26': + resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} - file-type@20.4.1: - resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==} - engines: {node: '>=18'} + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - finalhandler@1.3.2: - resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} - engines: {node: '>= 0.8'} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + '@types/node@20.19.39': + resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + '@types/nodemailer@8.0.0': + resolution: {integrity: sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} + '@types/passport-jwt@4.0.1': + resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} - fork-ts-checker-webpack-plugin@9.0.2: - resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} - engines: {node: '>=12.13.0', yarn: '>=1.0.0'} - peerDependencies: - typescript: '>3.6.0' - webpack: ^5.11.0 + '@types/passport-strategy@0.2.38': + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} - engines: {node: '>= 6'} + '@types/passport@1.0.17': + resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} - formidable@2.1.5: - resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==} + '@types/pg@8.20.0': + resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} + '@types/qs@6.15.0': + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - fs-monkey@1.1.0: - resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] + '@types/ua-parser-js@0.7.39': + resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + '@vitest/coverage-v8@4.1.4': + resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==} + peerDependencies: + '@vitest/browser': 4.1.4 + vitest: 4.1.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true + '@vitest/expect@4.1.4': + resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + '@vitest/mocker@4.1.4': + resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + '@vitest/pretty-format@4.1.4': + resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + '@vitest/runner@4.1.4': + resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + '@vitest/snapshot@4.1.4': + resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + '@vitest/spy@4.1.4': + resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + '@vitest/utils@4.1.4': + resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} - handlebars@4.7.9: - resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} - engines: {node: '>=0.4.7'} - hasBin: true + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} - has-own-prop@2.0.0: - resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} - engines: {node: '>=8'} + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} - hasBin: true + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + '@willsoto/nestjs-prometheus@6.1.0': + resolution: {integrity: sha512-lrCEnJBBSzUIYWGR+PsZw1YXs1B9jzxFEuNAa3RzTxuFAFdI+sW7Fp52il/U/dX2MWoHc32x06OS0nm56QwyzQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + prom-client: ^15.0.0 - inflight@1.0.6: - resolution: {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. + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - inquirer@8.2.6: - resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} - engines: {node: '>=12.0.0'} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} - inquirer@9.2.15: - resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} - engines: {node: '>=18'} + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + argon2@0.44.0: + resolution: {integrity: sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==} + engines: {node: '>=16.17.0'} - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - istanbul-lib-instrument@6.0.3: - resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} - engines: {node: '>=10'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} - istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} - iterare@1.2.1: - resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} - engines: {node: '>=6'} + avvio@9.2.0: + resolution: {integrity: sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} - jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - jest-cli@29.7.0: - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + baseline-browser-mapping@2.10.17: + resolution: {integrity: sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==} + engines: {node: '>=6.0.0'} hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - jest-config@29.7.0: - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} - jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + brace-expansion@1.1.13: + resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} - jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + bullmq@5.73.4: + resolution: {integrity: sha512-Q+NeFLtdKSD3GDPYSX4pH+Mc9E4OZVKimXwrnZ5WmndNy31COMy4vQV9zfhgfHGSUFrlpsBicfKYbSjx9FbO+A==} - jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} - jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} - jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + caniuse-lite@1.0.30001787: + resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} - jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} - jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} + check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} - jest@29.7.0: - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} - hasBin: true + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} - jsonc-parser@3.2.1: - resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + comment-json@4.6.2: + resolution: {integrity: sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==} + engines: {node: '>= 6'} - loader-runner@4.3.1: - resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} - engines: {node: '>=6.11.5'} + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + conventional-changelog-angular@8.3.1: + resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==} + engines: {node: '>=18'} - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + conventional-changelog-conventionalcommits@9.3.1: + resolution: {integrity: sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==} + engines: {node: '>=18'} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + conventional-commits-parser@6.4.0: + resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} + engines: {node: '>=18'} + hasBin: true - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} - magic-string@0.30.8: - resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} - engines: {node: '>=12'} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} + engines: {node: '>=v18'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} + cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} + hasBin: true - memfs@3.5.3: - resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} - engines: {node: '>= 4.0.0'} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true + detect-europe-js@0.1.2: + resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==} - mime@2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} - engines: {node: '>=4.0.0'} - hasBin: true + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} + dotenv-expand@12.0.3: + resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} + engines: {node: '>=12'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} + dotenv@17.4.1: + resolution: {integrity: sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==} + engines: {node: '>=12'} - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + drizzle-kit@0.31.10: + resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==} hasBin: true - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + drizzle-orm@0.45.2: + resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + drizzle-zod@0.8.3: + resolution: {integrity: sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==} + peerDependencies: + drizzle-orm: '>=0.36.0' + zod: ^3.25.0 || ^4.0.0 - multer@2.0.2: - resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==} - engines: {node: '>= 10.16.0'} + duplexify@3.7.1: + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} - mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} - mute-stream@1.0.0: - resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} + electron-to-chromium@1.5.334: + resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==} - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - node-abort-controller@3.1.1: - resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - node-emoji@1.11.0: - resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} - node-releases@2.0.37: - resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} + error-causes@3.0.2: + resolution: {integrity: sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw==} - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} - path-to-regexp@0.1.13: - resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} - path-to-regexp@3.3.0: - resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - picomatch@4.0.1: - resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} - engines: {node: '>=12'} + fast-json-stringify@6.3.0: + resolution: {integrity: sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==} - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - prettier-linter-helpers@1.0.1: - resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} - engines: {node: '>=6.0.0'} + fast-xml-builder@1.1.4: + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} - prettier@3.8.2: - resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} - engines: {node: '>=14'} + fast-xml-parser@5.5.8: + resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} hasBin: true - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} + fastify-plugin@5.1.0: + resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} + fastify@5.8.4: + resolution: {integrity: sha512-sa42J1xylbBAYUWALSBoyXKPDUvM3OoNOibIefA+Oha57FryXKKCZarA1iDntOCWp3O35voZLuDg2mdODXtPzQ==} - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true - qs@6.14.2: - resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} - engines: {node: '>=0.6'} + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} - qs@6.15.1: - resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} - engines: {node: '>=0.6'} + file-type@21.3.4: + resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} + engines: {node: '>=20'} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} - raw-body@2.5.3: - resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} - engines: {node: '>= 0.8'} + find-my-way@9.5.0: + resolution: {integrity: sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==} + engines: {node: '>=20'} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + fork-ts-checker-webpack-plugin@9.1.0: + resolution: {integrity: sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==} + engines: {node: '>=14.21.3'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + fs-monkey@1.1.0: + resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} - resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} - engines: {node: '>=10'} + get-tsconfig@4.13.7: + resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} + git-raw-commits@5.0.1: + resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==} + engines: {node: '>=18'} hasBin: true - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} - run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} - run-async@3.0.0: - resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} - engines: {node: '>=0.12.0'} + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} - schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - schema-utils@4.3.3: - resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} - engines: {node: '>= 10.13.0'} + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} hasBin: true - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} - send@0.19.2: - resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} - engines: {node: '>= 0.8.0'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - serve-static@1.16.3: - resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} - engines: {node: '>= 0.8.0'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + inflight@1.0.6: + resolution: {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. - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} + ioredis@5.10.1: + resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} + engines: {node: '>=12.22.0'} - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} + engines: {node: '>= 10'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + is-standalone-pwa@0.1.1: + resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - strtok3@10.3.5: - resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} - engines: {node: '>=18'} + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} - superagent@8.1.2: - resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} - engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true - supertest@6.3.4: - resolution: {integrity: sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==} - engines: {node: '>=6.4.0'} - deprecated: Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - symbol-observable@4.0.0: - resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} - engines: {node: '>=0.10'} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true - synckit@0.11.12: - resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} - engines: {node: ^14.18.0 || >=16.0.0} + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - tapable@2.3.2: - resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} - engines: {node: '>=6'} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - terser-webpack-plugin@5.4.0: - resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true + json-schema-ref-resolver@3.0.0: + resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} - terser@5.46.1: - resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} - engines: {node: '>=10'} - hasBin: true + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - token-types@6.1.2: - resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} - engines: {node: '>=14.16'} + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} - ts-jest@29.4.9: - resolution: {integrity: sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==} - engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - 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 - esbuild: '*' - jest: ^29.0.0 || ^30.0.0 - jest-util: ^29.0.0 || ^30.0.0 - typescript: '>=4.3 <7' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/transform': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - jest-util: - optional: true + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] - ts-loader@9.5.7: - resolution: {integrity: sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==} - engines: {node: '>=12.0.0'} - peerDependencies: - typescript: '*' - webpack: ^5.0.0 + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - 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 + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] - tsconfig-paths-webpack-plugin@4.2.0: - resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} - engines: {node: '>=10.13.0'} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} + hasBin: true - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} + load-esm@1.0.3: + resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} + engines: {node: '>=13.2.0'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} + engines: {node: '>=6.11.5'} - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} - engines: {node: '>=14.17'} - hasBin: true + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - uid@2.0.2: - resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} - engines: {node: '>=8'} + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - uint8array-extras@1.5.0: - resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} - engines: {node: '>=18'} + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} + lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + lodash.mergewith@4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash.upperfirst@4.3.1: + resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + lru-cache@11.3.3: + resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==} + engines: {node: 20 || >=22} + + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true + + msgpackr@1.11.5: + resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==} + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nestjs-zod@5.3.0: + resolution: {integrity: sha512-QY6imXm9heMOpWigjFHgMWPvc1ZQHeNQ7pdogo9Q5xj5F8HpqZ972vKlVdkaTyzYlOXJP/yVy3wlF1EjubDQPg==} peerDependencies: - browserslist: '>= 4.21.0' + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/swagger': ^7.4.2 || ^8.0.0 || ^11.0.0 + rxjs: ^7.0.0 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + '@nestjs/swagger': + optional: true - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + node-addon-api@8.7.0: + resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} + engines: {node: ^18 || ^20 || >= 21} - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + + nodemailer@8.0.5: + resolution: {integrity: sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==} + engines: {node: '>=6.0.0'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + otplib@13.4.0: + resolution: {integrity: sha512-RUcYcRMCgRWhUE/XabRppXpUwCwaWBNHe5iPXhdvP8wwDGpGpsIf/kxX/ec3zFsOaM1Oq8lEhUqDwk6W7DHkwg==} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + + passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + + passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} engines: {node: '>= 0.4.0'} - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + + peek-stream@1.1.3: + resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} + + pg-cloudflare@1.3.0: + resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + + pg-connection-string@2.12.0: + resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.13.0: + resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.20.0: + resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + postcss@8.5.9: + resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} + engines: {node: ^10 || ^12 || >=14} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.2: + resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} + engines: {node: '>=14'} + hasBin: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + prom-client@15.1.3: + resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} + engines: {node: ^16 || ^18 || >=20} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + pumpify@2.0.1: + resolution: {integrity: sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + redis-info@3.1.0: + resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rolldown@1.0.0-rc.15: + resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex2@5.1.0: + resolution: {integrity: sha512-pNHAuBW7TrcleFHsxBr5QMi/Iyp0ENjUKz7GCcX1UO7cMh+NmVK6HxQckNL1tJp1XAJVjG6B8OKIPqodqj9rtw==} + hasBin: true + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + engines: {node: '>= 10.13.0'} + + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.0: + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + engines: {node: '>=20'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strnum@2.2.3: + resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} + + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + swagger-ui-dist@5.32.2: + resolution: {integrity: sha512-t6Ns52nS8LU2hqi0+rezMjFO1ZrCsCrnommXrU7Nfrg2va2dWahdvM6TuSwzdHpG29v6BHJyU1c/UWFhgVZzVQ==} + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + engines: {node: '>=6'} + + tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + + terser-webpack-plugin@5.4.0: + resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.46.1: + resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} + engines: {node: '>=10'} + hasBin: true + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + + transliteration@2.6.1: + resolution: {integrity: sha512-hJ9BhrQAOnNTbpOr1MxsNjZISkn7ppvF5TKUeFmTE1mG4ZPD/XVxF0L0LUoIUCWmQyxH0gJpVtfYLAWf298U9w==} + engines: {node: '>=20.0.0'} + hasBin: true + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-loader@9.5.7: + resolution: {integrity: sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 + + tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ua-is-frozen@0.1.2: + resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} + + ua-parser-js@2.0.9: + resolution: {integrity: sha512-OsqGhxyo/wGdLSXMSJxuMGN6H4gDnKz6Fb3IBm4bxZFMnyy0sdf6MN96Ie8tC6z/btdO+Bsy8guxlvLdwT076w==} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + vite@8.0.8: + resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.4: + resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.4 + '@vitest/browser-preview': 4.1.4 + '@vitest/browser-webdriverio': 4.1.4 + '@vitest/coverage-istanbul': 4.1.4 + '@vitest/coverage-v8': 4.1.4 + '@vitest/ui': 4.1.4 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + engines: {node: '>=10.13.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} + engines: {node: '>=10.13.0'} + + webpack@5.106.0: + resolution: {integrity: sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@angular-devkit/core@19.2.23(chokidar@4.0.3)': + dependencies: + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + jsonc-parser: 3.3.1 + picomatch: 4.0.4 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/core@19.2.24(chokidar@4.0.3)': + dependencies: + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + jsonc-parser: 3.3.1 + picomatch: 4.0.4 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/schematics-cli@19.2.24(@types/node@20.19.39)(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.24(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) + '@inquirer/prompts': 7.3.2(@types/node@20.19.39) + ansi-colors: 4.1.3 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - '@types/node' + - chokidar + + '@angular-devkit/schematics@19.2.23(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.23(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.24(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1029.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-bucket-endpoint': 3.972.9 + '@aws-sdk/middleware-expect-continue': 3.972.9 + '@aws-sdk/middleware-flexible-checksums': 3.974.7 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-location-constraint': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-sdk-s3': 3.972.28 + '@aws-sdk/middleware-ssec': 3.972.9 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/signature-v4-multi-region': 3.996.16 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/eventstream-serde-browser': 4.2.13 + '@smithy/eventstream-serde-config-resolver': 4.3.13 + '@smithy/eventstream-serde-node': 4.2.13 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-blob-browser': 4.2.14 + '@smithy/hash-node': 4.2.13 + '@smithy/hash-stream-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/md5-js': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.973.27': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws-sdk/xml-builder': 3.972.17 + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.6': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.25': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.27': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/node-http-handler': 4.5.2 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-stream': 4.5.22 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-env': 3.972.25 + '@aws-sdk/credential-provider-http': 3.972.27 + '@aws-sdk/credential-provider-login': 3.972.29 + '@aws-sdk/credential-provider-process': 3.972.25 + '@aws-sdk/credential-provider-sso': 3.972.29 + '@aws-sdk/credential-provider-web-identity': 3.972.29 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.30': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.25 + '@aws-sdk/credential-provider-http': 3.972.27 + '@aws-sdk/credential-provider-ini': 3.972.29 + '@aws-sdk/credential-provider-process': 3.972.25 + '@aws-sdk/credential-provider-sso': 3.972.29 + '@aws-sdk/credential-provider-web-identity': 3.972.29 + '@aws-sdk/types': 3.973.7 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.25': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/token-providers': 3.1026.0 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-bucket-endpoint@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.974.7': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/crc64-nvme': 3.972.6 + '@aws-sdk/types': 3.973.7 + '@smithy/is-array-buffer': 4.2.2 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.28': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@smithy/core': 3.23.14 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-retry': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.996.19': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/config-resolver': 4.4.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1029.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.996.16 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-format-url': 3.972.9 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.16': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.28 + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1026.0': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.7': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.972.3': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.6': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-endpoints': 3.3.4 + tslib: 2.8.1 + + '@aws-sdk/util-format-url@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/querystring-builder': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.15': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/types': 3.973.7 + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.17': + dependencies: + '@smithy/types': 4.14.0 + fast-xml-parser: 5.5.8 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@borewit/text-codec@0.2.2': {} + + '@bull-board/api@6.21.0(@bull-board/ui@6.21.0)': + dependencies: + '@bull-board/ui': 6.21.0 + redis-info: 3.1.0 + + '@bull-board/fastify@6.21.0': + dependencies: + '@bull-board/api': 6.21.0(@bull-board/ui@6.21.0) + '@bull-board/ui': 6.21.0 + '@fastify/static': 9.1.0 + '@fastify/view': 11.1.1 + ejs: 3.1.10 + + '@bull-board/nestjs@6.21.0(@bull-board/api@6.21.0(@bull-board/ui@6.21.0))(@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@bull-board/api': 6.21.0(@bull-board/ui@6.21.0) + '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + + '@bull-board/ui@6.21.0': + dependencies: + '@bull-board/api': 6.21.0(@bull-board/ui@6.21.0) + + '@colors/colors@1.5.0': + optional: true + + '@commitlint/cli@20.5.0(@types/node@20.19.39)(conventional-commits-parser@6.4.0)(typescript@5.9.3)': + dependencies: + '@commitlint/format': 20.5.0 + '@commitlint/lint': 20.5.0 + '@commitlint/load': 20.5.0(@types/node@20.19.39)(typescript@5.9.3) + '@commitlint/read': 20.5.0(conventional-commits-parser@6.4.0) + '@commitlint/types': 20.5.0 + tinyexec: 1.1.1 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - conventional-commits-filter + - conventional-commits-parser + - typescript + + '@commitlint/config-conventional@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + conventional-changelog-conventionalcommits: 9.3.1 + + '@commitlint/config-validator@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + ajv: 8.18.0 + + '@commitlint/ensure@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 + + '@commitlint/execute-rule@20.0.0': {} + + '@commitlint/format@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + picocolors: 1.1.1 + + '@commitlint/is-ignored@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + semver: 7.7.4 + + '@commitlint/lint@20.5.0': + dependencies: + '@commitlint/is-ignored': 20.5.0 + '@commitlint/parse': 20.5.0 + '@commitlint/rules': 20.5.0 + '@commitlint/types': 20.5.0 + + '@commitlint/load@20.5.0(@types/node@20.19.39)(typescript@5.9.3)': + dependencies: + '@commitlint/config-validator': 20.5.0 + '@commitlint/execute-rule': 20.0.0 + '@commitlint/resolve-extends': 20.5.0 + '@commitlint/types': 20.5.0 + cosmiconfig: 9.0.1(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@20.19.39)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3) + is-plain-obj: 4.1.0 + lodash.mergewith: 4.6.2 + picocolors: 1.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@20.4.3': {} + + '@commitlint/parse@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + conventional-changelog-angular: 8.3.1 + conventional-commits-parser: 6.4.0 + + '@commitlint/read@20.5.0(conventional-commits-parser@6.4.0)': + dependencies: + '@commitlint/top-level': 20.4.3 + '@commitlint/types': 20.5.0 + git-raw-commits: 5.0.1(conventional-commits-parser@6.4.0) + minimist: 1.2.8 + tinyexec: 1.1.1 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + + '@commitlint/resolve-extends@20.5.0': + dependencies: + '@commitlint/config-validator': 20.5.0 + '@commitlint/types': 20.5.0 + global-directory: 4.0.1 + import-meta-resolve: 4.2.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + + '@commitlint/rules@20.5.0': + dependencies: + '@commitlint/ensure': 20.5.0 + '@commitlint/message': 20.4.3 + '@commitlint/to-lines': 20.0.0 + '@commitlint/types': 20.5.0 + + '@commitlint/to-lines@20.0.0': {} + + '@commitlint/top-level@20.4.3': + dependencies: + escalade: 3.2.0 + + '@commitlint/types@20.5.0': + dependencies: + conventional-commits-parser: 6.4.0 + picocolors: 1.1.1 + + '@conventional-changelog/git-client@2.7.0(conventional-commits-parser@6.4.0)': + dependencies: + '@simple-libs/child-process-utils': 1.0.2 + '@simple-libs/stream-utils': 1.2.0 + semver: 7.7.4 + optionalDependencies: + conventional-commits-parser: 6.4.0 + + '@drizzle-team/brocli@0.10.2': {} + + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@epic-web/invariant@1.0.0': {} + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.13.7 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@fastify/accept-negotiator@2.0.1': {} + + '@fastify/ajv-compiler@4.0.5': + dependencies: + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + fast-uri: 3.1.0 + + '@fastify/busboy@3.2.0': {} + + '@fastify/compress@8.3.1': + dependencies: + '@fastify/accept-negotiator': 2.0.1 + fastify-plugin: 5.1.0 + mime-db: 1.52.0 + minipass: 7.1.3 + peek-stream: 1.1.3 + pump: 3.0.4 + pumpify: 2.0.1 + readable-stream: 4.7.0 + + '@fastify/cookie@11.0.2': + dependencies: + cookie: 1.1.1 + fastify-plugin: 5.1.0 + + '@fastify/cors@11.2.0': + dependencies: + fastify-plugin: 5.1.0 + toad-cache: 3.7.0 + + '@fastify/csrf-protection@7.1.0': + dependencies: + '@fastify/csrf': 8.0.1 + '@fastify/error': 4.2.0 + fastify-plugin: 5.1.0 + + '@fastify/csrf@8.0.1': {} + + '@fastify/deepmerge@3.2.1': {} + + '@fastify/error@4.2.0': {} + + '@fastify/fast-json-stringify-compiler@5.0.3': + dependencies: + fast-json-stringify: 6.3.0 + + '@fastify/formbody@8.0.2': + dependencies: + fast-querystring: 1.1.2 + fastify-plugin: 5.1.0 + + '@fastify/forwarded@3.0.1': {} + + '@fastify/merge-json-schemas@0.2.1': + dependencies: + dequal: 2.0.3 + + '@fastify/multipart@10.0.0': + dependencies: + '@fastify/busboy': 3.2.0 + '@fastify/deepmerge': 3.2.1 + '@fastify/error': 4.2.0 + fastify-plugin: 5.1.0 + secure-json-parse: 4.1.0 + + '@fastify/proxy-addr@5.1.0': + dependencies: + '@fastify/forwarded': 3.0.1 + ipaddr.js: 2.3.0 + + '@fastify/send@4.1.0': + dependencies: + '@lukeed/ms': 2.0.2 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.1 + mime: 3.0.0 + + '@fastify/static@9.1.0': + dependencies: + '@fastify/accept-negotiator': 2.0.1 + '@fastify/send': 4.1.0 + content-disposition: 1.1.0 + fastify-plugin: 5.1.0 + fastq: 1.20.1 + glob: 13.0.6 + + '@fastify/view@11.1.1': + dependencies: + fastify-plugin: 5.1.0 + toad-cache: 3.7.0 + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@20.19.39)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@20.19.39) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 20.19.39 + + '@inquirer/confirm@5.1.21(@types/node@20.19.39)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/type': 3.0.10(@types/node@20.19.39) + optionalDependencies: + '@types/node': 20.19.39 + + '@inquirer/core@10.3.2(@types/node@20.19.39)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@20.19.39) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 20.19.39 + + '@inquirer/editor@4.2.23(@types/node@20.19.39)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/external-editor': 1.0.3(@types/node@20.19.39) + '@inquirer/type': 3.0.10(@types/node@20.19.39) + optionalDependencies: + '@types/node': 20.19.39 + + '@inquirer/expand@4.0.23(@types/node@20.19.39)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/type': 3.0.10(@types/node@20.19.39) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 20.19.39 + + '@inquirer/external-editor@1.0.3(@types/node@20.19.39)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 20.19.39 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@20.19.39)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/type': 3.0.10(@types/node@20.19.39) + optionalDependencies: + '@types/node': 20.19.39 + + '@inquirer/number@3.0.23(@types/node@20.19.39)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/type': 3.0.10(@types/node@20.19.39) + optionalDependencies: + '@types/node': 20.19.39 - watchpack@2.5.1: - resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} - engines: {node: '>=10.13.0'} + '@inquirer/password@4.0.23(@types/node@20.19.39)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/type': 3.0.10(@types/node@20.19.39) + optionalDependencies: + '@types/node': 20.19.39 - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + '@inquirer/prompts@7.10.1(@types/node@20.19.39)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@20.19.39) + '@inquirer/confirm': 5.1.21(@types/node@20.19.39) + '@inquirer/editor': 4.2.23(@types/node@20.19.39) + '@inquirer/expand': 4.0.23(@types/node@20.19.39) + '@inquirer/input': 4.3.1(@types/node@20.19.39) + '@inquirer/number': 3.0.23(@types/node@20.19.39) + '@inquirer/password': 4.0.23(@types/node@20.19.39) + '@inquirer/rawlist': 4.1.11(@types/node@20.19.39) + '@inquirer/search': 3.2.2(@types/node@20.19.39) + '@inquirer/select': 4.4.2(@types/node@20.19.39) + optionalDependencies: + '@types/node': 20.19.39 - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + '@inquirer/prompts@7.3.2(@types/node@20.19.39)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@20.19.39) + '@inquirer/confirm': 5.1.21(@types/node@20.19.39) + '@inquirer/editor': 4.2.23(@types/node@20.19.39) + '@inquirer/expand': 4.0.23(@types/node@20.19.39) + '@inquirer/input': 4.3.1(@types/node@20.19.39) + '@inquirer/number': 3.0.23(@types/node@20.19.39) + '@inquirer/password': 4.0.23(@types/node@20.19.39) + '@inquirer/rawlist': 4.1.11(@types/node@20.19.39) + '@inquirer/search': 3.2.2(@types/node@20.19.39) + '@inquirer/select': 4.4.2(@types/node@20.19.39) + optionalDependencies: + '@types/node': 20.19.39 - webpack-node-externals@3.0.0: - resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} - engines: {node: '>=6'} + '@inquirer/rawlist@4.1.11(@types/node@20.19.39)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/type': 3.0.10(@types/node@20.19.39) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 20.19.39 - webpack-sources@3.3.4: - resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} - engines: {node: '>=10.13.0'} + '@inquirer/search@3.2.2(@types/node@20.19.39)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@20.19.39) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 20.19.39 - webpack@5.97.1: - resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true + '@inquirer/select@4.4.2(@types/node@20.19.39)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@20.19.39) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@20.19.39) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 20.19.39 - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + '@inquirer/type@3.0.10(@types/node@20.19.39)': + optionalDependencies: + '@types/node': 20.19.39 - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + '@ioredis/commands@1.5.1': {} - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + '@jridgewell/resolve-uri@3.1.2': {} - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + '@jridgewell/sourcemap-codec@1.5.5': {} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + '@lukeed/csprng@1.1.0': {} - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + '@lukeed/ms@2.0.2': {} - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} + '@microsoft/tsdoc@0.16.0': {} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true -snapshots: + '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true - '@angular-devkit/core@17.3.11(chokidar@3.6.0)': + '@nestjs-modules/ioredis@2.2.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(ioredis@5.10.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) - jsonc-parser: 3.2.1 - picomatch: 4.0.1 - rxjs: 7.8.1 - source-map: 0.7.4 + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + ioredis: 5.10.1 optionalDependencies: - chokidar: 3.6.0 + '@nestjs/terminus': 11.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + transitivePeerDependencies: + - '@grpc/grpc-js' + - '@grpc/proto-loader' + - '@mikro-orm/core' + - '@mikro-orm/nestjs' + - '@nestjs/axios' + - '@nestjs/microservices' + - '@nestjs/mongoose' + - '@nestjs/sequelize' + - '@nestjs/typeorm' + - '@prisma/client' + - mongoose + - reflect-metadata + - rxjs + - sequelize + - typeorm + + '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))': + dependencies: + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + tslib: 2.8.1 - '@angular-devkit/schematics-cli@17.3.11(chokidar@3.6.0)': + '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.73.4)': dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) - ansi-colors: 4.1.3 - inquirer: 9.2.15 - symbol-observable: 4.0.0 - yargs-parser: 21.1.1 - transitivePeerDependencies: - - chokidar + '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + bullmq: 5.73.4 + tslib: 2.8.1 - '@angular-devkit/schematics@17.3.11(chokidar@3.6.0)': + '@nestjs/cli@11.0.19(@swc/core@1.15.24)(@types/node@20.19.39)(esbuild@0.27.7)': dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - jsonc-parser: 3.2.1 - magic-string: 0.30.8 + '@angular-devkit/core': 19.2.24(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) + '@angular-devkit/schematics-cli': 19.2.24(@types/node@20.19.39)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@20.19.39) + '@nestjs/schematics': 11.0.10(chokidar@4.0.3)(typescript@5.9.3) + ansis: 4.2.0 + chokidar: 4.0.3 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)) + glob: 13.0.6 + node-emoji: 1.11.0 ora: 5.4.1 - rxjs: 7.8.1 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.2.0 + typescript: 5.9.3 + webpack: 5.106.0(@swc/core@1.15.24)(esbuild@0.27.7) + webpack-node-externals: 3.0.0 + optionalDependencies: + '@swc/core': 1.15.24 transitivePeerDependencies: - - chokidar - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.0': {} + - '@types/node' + - esbuild + - uglify-js + - webpack-cli - '@babel/core@7.29.0': + '@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 + file-type: 21.3.4 + iterare: 1.2.1 + load-esm: 1.0.3 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 transitivePeerDependencies: - supports-color - '@babel/generator@7.29.1': + '@nestjs/config@4.0.4(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + dotenv: 17.4.1 + dotenv-expand: 12.0.3 + lodash: 4.18.1 + rxjs: 7.8.2 - '@babel/helper-compilation-targets@7.28.6': + '@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nuxt/opencollective': 0.4.1 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 8.4.2 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 - '@babel/helper-module-imports@7.28.6': + '@nestjs/jwt@11.0.2(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))': dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@types/jsonwebtoken': 9.0.10 + jsonwebtoken: 9.0.3 - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.28.6': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 - '@babel/helpers@7.29.2': + '@nestjs/passport@11.0.5(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)': dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + passport: 0.7.0 - '@babel/parser@7.29.2': + '@nestjs/platform-fastify@11.1.18(@fastify/static@9.1.0)(@fastify/view@11.1.1)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))': dependencies: - '@babel/types': 7.29.0 + '@fastify/cors': 11.2.0 + '@fastify/formbody': 8.0.2 + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + fast-querystring: 1.1.2 + fastify: 5.8.4 + fastify-plugin: 5.1.0 + find-my-way: 9.5.0 + light-my-request: 6.6.0 + path-to-regexp: 8.4.2 + reusify: 1.1.0 + tslib: 2.8.1 + optionalDependencies: + '@fastify/static': 9.1.0 + '@fastify/view': 11.1.1 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + '@nestjs/schematics@11.0.10(chokidar@4.0.3)(typescript@5.9.3)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@angular-devkit/core': 19.2.23(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.23(chokidar@4.0.3) + comment-json: 4.6.2 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.9.3 + transitivePeerDependencies: + - chokidar - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + '@nestjs/swagger@11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@microsoft/tsdoc': 0.16.0 + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) + js-yaml: 4.1.1 + lodash: 4.18.1 + path-to-regexp: 8.4.2 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.32.2 + optionalDependencies: + '@fastify/static': 9.1.0 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + '@nestjs/terminus@11.1.1(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + optional: true - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + '@nestjs/testing@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + tslib: 2.8.1 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + '@nestjs/throttler@6.5.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@noble/hashes@2.0.1': {} - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + '@nodelib/fs.scandir@2.1.5': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + '@nodelib/fs.walk@1.2.8': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + '@nuxt/opencollective@0.4.1': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + consola: 3.4.2 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + '@opentelemetry/api@1.9.1': {} + + '@otplib/core@13.4.0': {} + + '@otplib/hotp@13.4.0': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@otplib/core': 13.4.0 + '@otplib/uri': 13.4.0 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + '@otplib/plugin-base32-scure@13.4.0': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@otplib/core': 13.4.0 + '@scure/base': 2.0.0 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + '@otplib/plugin-crypto-noble@13.4.0': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@noble/hashes': 2.0.1 + '@otplib/core': 13.4.0 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + '@otplib/totp@13.4.0': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@otplib/core': 13.4.0 + '@otplib/hotp': 13.4.0 + '@otplib/uri': 13.4.0 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + '@otplib/uri@13.4.0': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@otplib/core': 13.4.0 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + '@oxc-project/types@0.124.0': {} + + '@paralleldrive/cuid2@3.3.0': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@noble/hashes': 2.0.1 + bignumber.js: 9.3.1 + error-causes: 3.0.2 + + '@phc/format@1.0.0': {} + + '@pinojs/redact@0.4.0': {} + + '@rolldown/binding-android-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + optional: true - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + optional: true - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.15': {} + + '@scarf/scarf@1.4.0': {} + + '@scure/base@2.0.0': {} + + '@simple-libs/child-process-utils@1.0.2': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@simple-libs/stream-utils': 1.2.0 + + '@simple-libs/stream-utils@1.2.0': {} - '@babel/template@7.28.6': + '@smithy/chunked-blob-reader-native@4.2.3': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 - '@babel/traverse@7.29.0': + '@smithy/chunked-blob-reader@5.2.2': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color + tslib: 2.8.1 - '@babel/types@7.29.0': + '@smithy/config-resolver@4.4.14': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + tslib: 2.8.1 - '@bcoe/v8-coverage@0.2.3': {} + '@smithy/core@3.23.14': + dependencies: + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 - '@borewit/text-codec@0.2.2': {} + '@smithy/credential-provider-imds@4.2.13': + dependencies: + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + tslib: 2.8.1 - '@colors/colors@1.5.0': - optional: true + '@smithy/eventstream-codec@4.2.13': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.0 + '@smithy/util-hex-encoding': 4.2.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.13': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@cspotcode/source-map-support@0.8.1': + '@smithy/eventstream-serde-config-resolver@4.3.13': dependencies: - '@jridgewell/trace-mapping': 0.3.9 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + '@smithy/eventstream-serde-node@4.2.13': dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} + '@smithy/eventstream-serde-universal': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@eslint/eslintrc@2.1.4': + '@smithy/eventstream-serde-universal@4.2.13': dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.1': {} + '@smithy/eventstream-codec': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@humanwhocodes/config-array@0.13.0': + '@smithy/fetch-http-handler@5.3.16': dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color + '@smithy/protocol-http': 5.3.13 + '@smithy/querystring-builder': 4.2.13 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 - '@humanwhocodes/module-importer@1.0.1': {} + '@smithy/hash-blob-browser@4.2.14': + dependencies: + '@smithy/chunked-blob-reader': 5.2.2 + '@smithy/chunked-blob-reader-native': 4.2.3 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@humanwhocodes/object-schema@2.0.3': {} + '@smithy/hash-node@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@isaacs/cliui@8.0.2': + '@smithy/hash-stream-node@4.2.13': dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + '@smithy/types': 4.14.0 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@istanbuljs/load-nyc-config@1.1.0': + '@smithy/invalid-dependency@4.2.13': dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.2 - resolve-from: 5.0.0 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@istanbuljs/schema@0.1.3': {} + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 - '@jest/console@29.7.0': + '@smithy/is-array-buffer@4.2.2': dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 + tslib: 2.8.1 - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))': + '@smithy/md5-js@4.2.13': dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node + '@smithy/types': 4.14.0 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@jest/environment@29.7.0': + '@smithy/middleware-content-length@4.2.13': dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - jest-mock: 29.7.0 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jest/expect-utils@29.7.0': + '@smithy/middleware-endpoint@4.4.29': dependencies: - jest-get-type: 29.6.3 + '@smithy/core': 3.23.14 + '@smithy/middleware-serde': 4.2.17 + '@smithy/node-config-provider': 4.3.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-middleware': 4.2.13 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.5.1': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/service-error-classification': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 - '@jest/expect@29.7.0': + '@smithy/middleware-serde@4.2.17': dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color + '@smithy/core': 3.23.14 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jest/fake-timers@29.7.0': + '@smithy/middleware-stack@4.2.13': dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.19.39 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jest/globals@29.7.0': + '@smithy/node-config-provider@4.3.13': dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jest/reporters@29.7.0': + '@smithy/node-http-handler@4.5.2': dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 20.19.39 - chalk: 4.1.2 - collect-v8-coverage: 1.0.3 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.2.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - jest-worker: 29.7.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.3.0 - transitivePeerDependencies: - - supports-color + '@smithy/protocol-http': 5.3.13 + '@smithy/querystring-builder': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jest/schemas@29.6.3': + '@smithy/property-provider@4.2.13': dependencies: - '@sinclair/typebox': 0.27.10 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jest/source-map@29.6.3': + '@smithy/protocol-http@5.3.13': dependencies: - '@jridgewell/trace-mapping': 0.3.31 - callsites: 3.1.0 - graceful-fs: 4.2.11 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jest/test-result@29.7.0': + '@smithy/querystring-builder@4.2.13': dependencies: - '@jest/console': 29.7.0 - '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.3 + '@smithy/types': 4.14.0 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 - '@jest/test-sequencer@29.7.0': + '@smithy/querystring-parser@4.2.13': dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.0.0 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jest/transform@29.7.0': + '@smithy/service-error-classification@4.2.13': dependencies: - '@babel/core': 7.29.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.31 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.8 - pirates: 4.0.7 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color + '@smithy/types': 4.14.0 - '@jest/types@29.6.3': + '@smithy/shared-ini-file-loader@4.4.8': dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.39 - '@types/yargs': 17.0.35 - chalk: 4.1.2 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jridgewell/gen-mapping@0.3.13': + '@smithy/signature-v4@5.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@jridgewell/remapping@2.3.5': + '@smithy/smithy-client@4.12.9': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@smithy/core': 3.23.14 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-stack': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-stream': 4.5.22 + tslib: 2.8.1 - '@jridgewell/resolve-uri@3.1.2': {} + '@smithy/types@4.14.0': + dependencies: + tslib: 2.8.1 - '@jridgewell/source-map@0.3.11': + '@smithy/url-parser@4.2.13': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@smithy/querystring-parser': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@jridgewell/sourcemap-codec@1.5.5': {} + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@jridgewell/trace-mapping@0.3.31': + '@smithy/util-body-length-browser@4.2.2': dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + tslib: 2.8.1 - '@jridgewell/trace-mapping@0.3.9': + '@smithy/util-body-length-node@4.2.3': dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + tslib: 2.8.1 - '@ljharb/through@2.3.14': + '@smithy/util-buffer-from@2.2.0': dependencies: - call-bind: 1.0.9 + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 - '@lukeed/csprng@1.1.0': {} + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 - '@nestjs/cli@10.4.9': + '@smithy/util-config-provider@4.2.2': dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0) - '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2) - chalk: 4.1.2 - chokidar: 3.6.0 - cli-table3: 0.6.5 - commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1) - glob: 10.4.5 - inquirer: 8.2.6 - node-emoji: 1.11.0 - ora: 5.4.1 - tree-kill: 1.2.2 - tsconfig-paths: 4.2.0 - tsconfig-paths-webpack-plugin: 4.2.0 - typescript: 5.7.2 - webpack: 5.97.1 - webpack-node-externals: 3.0.0 - transitivePeerDependencies: - - esbuild - - uglify-js - - webpack-cli + tslib: 2.8.1 - '@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@smithy/util-defaults-mode-browser@4.3.45': dependencies: - file-type: 20.4.1 - iterare: 1.2.1 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 + '@smithy/property-provider': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 tslib: 2.8.1 - uid: 2.0.2 - transitivePeerDependencies: - - supports-color - '@nestjs/core@10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@smithy/util-defaults-mode-node@4.2.49': dependencies: - '@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nuxtjs/opencollective': 0.3.2 - fast-safe-stringify: 2.1.1 - iterare: 1.2.1 - path-to-regexp: 3.3.0 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 + '@smithy/config-resolver': 4.4.14 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 tslib: 2.8.1 - uid: 2.0.2 - optionalDependencies: - '@nestjs/platform-express': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22) - transitivePeerDependencies: - - encoding - '@nestjs/platform-express@10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)': + '@smithy/util-endpoints@3.3.4': dependencies: - '@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2) - body-parser: 1.20.4 - cors: 2.8.5 - express: 4.22.1 - multer: 2.0.2 + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 tslib: 2.8.1 - transitivePeerDependencies: - - supports-color - '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.7.2)': + '@smithy/util-hex-encoding@4.2.2': dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) - comment-json: 4.2.5 - jsonc-parser: 3.3.1 - pluralize: 8.0.0 - typescript: 5.7.2 - transitivePeerDependencies: - - chokidar + tslib: 2.8.1 - '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.9.3)': + '@smithy/util-middleware@4.2.13': dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) - comment-json: 4.2.5 - jsonc-parser: 3.3.1 - pluralize: 8.0.0 - typescript: 5.9.3 - transitivePeerDependencies: - - chokidar + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@nestjs/testing@10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22)': + '@smithy/util-retry@4.3.1': dependencies: - '@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@smithy/service-error-classification': 4.2.13 + '@smithy/types': 4.14.0 tslib: 2.8.1 - optionalDependencies: - '@nestjs/platform-express': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22) - '@noble/hashes@1.8.0': {} + '@smithy/util-stream@4.5.22': + dependencies: + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/node-http-handler': 4.5.2 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@nodelib/fs.scandir@2.1.5': + '@smithy/util-uri-escape@4.2.2': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + tslib: 2.8.1 - '@nodelib/fs.stat@2.0.5': {} + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 - '@nodelib/fs.walk@1.2.8': + '@smithy/util-utf8@4.2.2': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 - '@nuxtjs/opencollective@0.3.2': + '@smithy/util-waiter@4.2.15': dependencies: - chalk: 4.1.2 - consola: 2.15.3 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@paralleldrive/cuid2@2.3.1': + '@smithy/uuid@1.1.2': dependencies: - '@noble/hashes': 1.8.0 + tslib: 2.8.1 - '@pkgjs/parseargs@0.11.0': + '@standard-schema/spec@1.1.0': {} + + '@swc/core-darwin-arm64@1.15.24': optional: true - '@pkgr/core@0.2.9': {} + '@swc/core-darwin-x64@1.15.24': + optional: true - '@sinclair/typebox@0.27.10': {} + '@swc/core-linux-arm-gnueabihf@1.15.24': + optional: true - '@sinonjs/commons@3.0.1': - dependencies: - type-detect: 4.0.8 + '@swc/core-linux-arm64-gnu@1.15.24': + optional: true - '@sinonjs/fake-timers@10.3.0': - dependencies: - '@sinonjs/commons': 3.0.1 + '@swc/core-linux-arm64-musl@1.15.24': + optional: true - '@tokenizer/inflate@0.2.7': - dependencies: - debug: 4.4.3 - fflate: 0.8.2 - token-types: 6.1.2 - transitivePeerDependencies: - - supports-color + '@swc/core-linux-ppc64-gnu@1.15.24': + optional: true - '@tokenizer/token@0.3.0': {} + '@swc/core-linux-s390x-gnu@1.15.24': + optional: true + + '@swc/core-linux-x64-gnu@1.15.24': + optional: true - '@tsconfig/node10@1.0.12': {} + '@swc/core-linux-x64-musl@1.15.24': + optional: true - '@tsconfig/node12@1.0.11': {} + '@swc/core-win32-arm64-msvc@1.15.24': + optional: true - '@tsconfig/node14@1.0.3': {} + '@swc/core-win32-ia32-msvc@1.15.24': + optional: true - '@tsconfig/node16@1.0.4': {} + '@swc/core-win32-x64-msvc@1.15.24': + optional: true - '@types/babel__core@7.20.5': + '@swc/core@1.15.24': dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 + '@swc/counter': 0.1.3 + '@swc/types': 0.1.26 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.24 + '@swc/core-darwin-x64': 1.15.24 + '@swc/core-linux-arm-gnueabihf': 1.15.24 + '@swc/core-linux-arm64-gnu': 1.15.24 + '@swc/core-linux-arm64-musl': 1.15.24 + '@swc/core-linux-ppc64-gnu': 1.15.24 + '@swc/core-linux-s390x-gnu': 1.15.24 + '@swc/core-linux-x64-gnu': 1.15.24 + '@swc/core-linux-x64-musl': 1.15.24 + '@swc/core-win32-arm64-msvc': 1.15.24 + '@swc/core-win32-ia32-msvc': 1.15.24 + '@swc/core-win32-x64-msvc': 1.15.24 + optional: true + + '@swc/counter@0.1.3': + optional: true - '@types/babel__generator@7.27.0': + '@swc/types@0.1.26': dependencies: - '@babel/types': 7.29.0 + '@swc/counter': 0.1.3 + optional: true - '@types/babel__template@7.4.4': + '@tokenizer/inflate@0.4.1': dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} - '@types/babel__traverse@7.28.0': + '@tybys/wasm-util@0.10.1': dependencies: - '@babel/types': 7.29.0 + tslib: 2.8.1 + optional: true '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 '@types/node': 20.19.39 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/connect@3.4.38': dependencies: '@types/node': 20.19.39 - '@types/cookiejar@2.1.5': {} + '@types/deep-eql@4.0.2': {} '@types/eslint-scope@3.7.7': dependencies: @@ -3487,50 +6352,57 @@ snapshots: '@types/estree@1.0.8': {} - '@types/express-serve-static-core@4.19.8': + '@types/express-serve-static-core@5.1.1': dependencies: '@types/node': 20.19.39 '@types/qs': 6.15.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 - '@types/express@4.17.25': + '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.8 - '@types/qs': 6.15.0 - '@types/serve-static': 1.15.10 - - '@types/graceful-fs@4.1.9': - dependencies: - '@types/node': 20.19.39 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 '@types/http-errors@2.0.5': {} - '@types/istanbul-lib-coverage@2.0.6': {} + '@types/json-schema@7.0.15': {} - '@types/istanbul-lib-report@3.0.3': + '@types/jsonwebtoken@9.0.10': dependencies: - '@types/istanbul-lib-coverage': 2.0.6 + '@types/ms': 2.1.0 + '@types/node': 20.19.39 - '@types/istanbul-reports@3.0.4': + '@types/ms@2.1.0': {} + + '@types/node@20.19.39': dependencies: - '@types/istanbul-lib-report': 3.0.3 + undici-types: 6.21.0 - '@types/jest@29.5.14': + '@types/nodemailer@8.0.0': dependencies: - expect: 29.7.0 - pretty-format: 29.7.0 + '@types/node': 20.19.39 - '@types/json-schema@7.0.15': {} + '@types/passport-jwt@4.0.1': + dependencies: + '@types/jsonwebtoken': 9.0.10 + '@types/passport-strategy': 0.2.38 - '@types/methods@1.1.4': {} + '@types/passport-strategy@0.2.38': + dependencies: + '@types/express': 5.0.6 + '@types/passport': 1.0.17 - '@types/mime@1.3.5': {} + '@types/passport@1.0.17': + dependencies: + '@types/express': 5.0.6 - '@types/node@20.19.39': + '@types/pg@8.20.0': dependencies: - undici-types: 6.21.0 + '@types/node': 20.19.39 + pg-protocol: 1.13.0 + pg-types: 2.2.0 '@types/qs@6.15.0': {} @@ -3538,40 +6410,16 @@ snapshots: '@types/semver@7.7.1': {} - '@types/send@0.17.6': - dependencies: - '@types/mime': 1.3.5 - '@types/node': 20.19.39 - '@types/send@1.2.1': dependencies: '@types/node': 20.19.39 - '@types/serve-static@1.15.10': - dependencies: - '@types/http-errors': 2.0.5 - '@types/node': 20.19.39 - '@types/send': 0.17.6 - - '@types/stack-utils@2.0.3': {} - - '@types/superagent@8.1.9': - dependencies: - '@types/cookiejar': 2.1.5 - '@types/methods': 1.1.4 - '@types/node': 20.19.39 - form-data: 4.0.5 - - '@types/supertest@6.0.3': + '@types/serve-static@2.2.0': dependencies: - '@types/methods': 1.1.4 - '@types/superagent': 8.1.9 - - '@types/yargs-parser@21.0.3': {} + '@types/http-errors': 2.0.5 + '@types/node': 20.19.39 - '@types/yargs@17.0.35': - dependencies: - '@types/yargs-parser': 21.0.3 + '@types/ua-parser-js@0.7.39': {} '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': dependencies: @@ -3661,6 +6509,61 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@vitest/coverage-v8@4.1.4(vitest@4.1.4)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.4 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.0.0 + tinyrainbow: 3.1.0 + vitest: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + + '@vitest/expect@4.1.4': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 4.1.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + + '@vitest/pretty-format@4.1.4': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.4': + dependencies: + '@vitest/utils': 4.1.4 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.4': + dependencies: + '@vitest/pretty-format': 4.1.4 + '@vitest/utils': 4.1.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.4': {} + + '@vitest/utils@4.1.4': + dependencies: + '@vitest/pretty-format': 4.1.4 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -3737,30 +6640,36 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@willsoto/nestjs-prometheus@6.1.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(prom-client@15.1.3)': + dependencies: + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + prom-client: 15.1.3 + '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} - accepts@1.3.8: + abort-controller@3.0.0: dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 + event-target-shim: 5.0.1 - acorn-jsx@5.3.2(acorn@8.16.0): + abstract-logging@2.0.1: {} + + acorn-import-phases@1.0.4(acorn@8.16.0): dependencies: acorn: 8.16.0 - acorn-walk@8.3.5: + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 acorn@8.16.0: {} - ajv-formats@2.1.1(ajv@8.12.0): + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.12.0 + ajv: 8.18.0 - ajv-formats@2.1.1(ajv@8.18.0): + ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -3780,13 +6689,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.12.0: - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -3794,11 +6696,16 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + optional: true + ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: + ansi-escapes@7.3.0: dependencies: - type-fest: 0.21.3 + environment: 1.1.0 ansi-regex@5.0.1: {} @@ -3808,97 +6715,53 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.3: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.2 - - append-field@1.0.0: {} - - arg@4.1.3: {} + ansis@4.2.0: {} - argparse@1.0.10: + argon2@0.44.0: dependencies: - sprintf-js: 1.0.3 + '@phc/format': 1.0.0 + cross-env: 10.1.0 + node-addon-api: 8.7.0 + node-gyp-build: 4.8.4 argparse@2.0.1: {} - array-flatten@1.1.1: {} + array-ify@1.0.0: {} array-timsort@1.0.3: {} array-union@2.1.0: {} - asap@2.0.6: {} + assertion-error@2.0.1: {} - asynckit@0.4.0: {} - - babel-jest@29.7.0(@babel/core@7.29.0): + ast-v8-to-istanbul@1.0.0: dependencies: - '@babel/core': 7.29.0 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.29.0) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 - babel-plugin-istanbul@6.1.1: - dependencies: - '@babel/helper-plugin-utils': 7.28.6 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color + async@3.2.6: {} - babel-plugin-jest-hoist@29.6.3: + atomic-sleep@1.0.0: {} + + avvio@9.2.0: dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.28.0 - - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) - - babel-preset-jest@29.6.3(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + '@fastify/error': 4.2.0 + fastq: 1.20.1 balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} baseline-browser-mapping@2.10.17: {} - binary-extensions@2.3.0: {} + bignumber.js@9.3.1: {} + + bintrees@1.0.2: {} bl@4.1.0: dependencies: @@ -3906,22 +6769,19 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.4: + bowser@2.14.1: {} + + boxen@5.1.2: 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.1 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.14.2 - raw-body: 2.5.3 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + optional: true brace-expansion@1.1.13: dependencies: @@ -3932,6 +6792,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3944,13 +6808,7 @@ snapshots: node-releases: 2.0.37 update-browserslist-db: 1.2.3(browserslist@4.28.2) - bs-logger@0.2.6: - dependencies: - fast-json-stable-stringify: 2.1.0 - - bser@2.1.1: - dependencies: - node-int64: 0.4.0 + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} @@ -3959,70 +6817,59 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - busboy@1.6.0: - dependencies: - streamsearch: 1.1.0 - - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.9: + buffer@6.0.3: dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 + base64-js: 1.5.1 + ieee754: 1.2.1 - call-bound@1.0.4: + bullmq@5.73.4: dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 + cron-parser: 4.9.0 + ioredis: 5.10.1 + msgpackr: 1.11.5 + node-abort-controller: 3.1.1 + semver: 7.7.4 + tslib: 2.8.1 + uuid: 11.1.0 + transitivePeerDependencies: + - supports-color callsites@3.1.0: {} - camelcase@5.3.1: {} - - camelcase@6.3.0: {} + camelcase@6.3.0: + optional: true caniuse-lite@1.0.30001787: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.2: {} + chardet@2.1.1: {} - char-regex@1.0.2: {} - - chardet@0.7.0: {} + check-disk-space@3.4.0: + optional: true - chokidar@3.6.0: + chokidar@4.0.3: dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 + readdirp: 4.1.2 chrome-trace-event@1.0.4: {} - ci-info@3.9.0: {} - - cjs-module-lexer@1.4.3: {} + cli-boxes@2.2.1: + optional: true cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + cli-spinners@2.9.2: {} cli-table3@0.6.5: @@ -4031,7 +6878,10 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 - cli-width@3.0.0: {} + cli-truncate@5.2.0: + dependencies: + slice-ansi: 8.0.0 + string-width: 8.2.0 cli-width@4.1.0: {} @@ -4043,9 +6893,7 @@ snapshots: clone@1.0.4: {} - co@4.6.0: {} - - collect-v8-coverage@1.0.3: {} + cluster-key-slot@1.1.2: {} color-convert@2.0.1: dependencies: @@ -4053,81 +6901,82 @@ snapshots: color-name@1.1.4: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 + colorette@2.0.20: {} + + commander@14.0.3: {} commander@2.20.3: {} commander@4.1.1: {} - comment-json@4.2.5: + comment-json@4.6.2: dependencies: array-timsort: 1.0.3 - core-util-is: 1.0.3 esprima: 4.0.1 - has-own-prop: 2.0.0 - repeat-string: 1.6.1 - component-emitter@1.3.1: {} + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 concat-map@0.0.1: {} - concat-stream@2.0.0: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - typedarray: 0.0.6 + consola@3.4.2: {} - consola@2.15.3: {} + content-disposition@1.1.0: {} - content-disposition@0.5.4: + conventional-changelog-angular@8.3.1: dependencies: - safe-buffer: 5.2.1 + compare-func: 2.0.0 - content-type@1.0.5: {} - - convert-source-map@2.0.0: {} + conventional-changelog-conventionalcommits@9.3.1: + dependencies: + compare-func: 2.0.0 - cookie-signature@1.0.7: {} + conventional-commits-parser@6.4.0: + dependencies: + '@simple-libs/stream-utils': 1.2.0 + meow: 13.2.0 - cookie@0.7.2: {} + convert-source-map@2.0.0: {} - cookiejar@2.1.4: {} + cookie@1.1.1: {} core-util-is@1.0.3: {} - cors@2.8.5: + cosmiconfig-typescript-loader@6.3.0(@types/node@20.19.39)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3): dependencies: - object-assign: 4.1.1 - vary: 1.1.2 + '@types/node': 20.19.39 + cosmiconfig: 9.0.1(typescript@5.9.3) + jiti: 2.6.1 + typescript: 5.9.3 - cosmiconfig@8.3.6(typescript@5.7.2): + cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.7.2 + typescript: 5.9.3 - create-jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): + cosmiconfig@9.0.1(typescript@5.9.3): dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 - create-require@1.1.1: {} + cron-parser@4.9.0: + dependencies: + luxon: 3.7.2 + + cross-env@10.1.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 cross-spawn@7.0.6: dependencies: @@ -4135,16 +6984,10 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@2.6.9: - dependencies: - ms: 2.0.0 - debug@4.4.3: dependencies: ms: 2.1.3 - dedent@1.7.2: {} - deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -4153,28 +6996,15 @@ snapshots: dependencies: clone: 1.0.4 - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - delayed-stream@1.0.0: {} + denque@2.1.0: {} depd@2.0.0: {} - destroy@1.2.0: {} - - detect-newline@3.1.0: {} - - dezalgo@1.0.4: - dependencies: - asap: 2.0.6 - wrappy: 1.0.2 + dequal@2.0.3: {} - diff-sequences@29.6.3: {} + detect-europe-js@0.1.2: {} - diff@4.0.4: {} + detect-libc@2.1.2: {} dir-glob@3.0.1: dependencies: @@ -4184,75 +7014,173 @@ snapshots: dependencies: esutils: 2.0.3 - dunder-proto@1.0.1: + dot-prop@5.3.0: dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 + is-obj: 2.0.0 - eastasianwidth@0.2.0: {} + dotenv-expand@12.0.3: + dependencies: + dotenv: 16.6.1 - ee-first@1.1.1: {} + dotenv@16.6.1: {} - electron-to-chromium@1.5.334: {} + dotenv@17.4.1: {} - emittery@0.13.1: {} + drizzle-kit@0.31.10: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.12 + tsx: 4.21.0 - emoji-regex@8.0.0: {} + drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0): + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/pg': 8.20.0 + pg: 8.20.0 - emoji-regex@9.2.2: {} + drizzle-zod@0.8.3(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0))(zod@4.3.6): + dependencies: + drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0) + zod: 4.3.6 - encodeurl@2.0.0: {} + duplexify@3.7.1: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 2.3.8 + stream-shift: 1.0.3 - enhanced-resolve@5.20.1: + duplexify@4.1.3: dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.2 + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.3 - error-ex@1.3.4: + ecdsa-sig-formatter@1.0.11: dependencies: - is-arrayish: 0.2.1 + safe-buffer: 5.2.1 - es-define-property@1.0.1: {} + ejs@3.1.10: + dependencies: + jake: 10.9.4 - es-errors@1.3.0: {} + electron-to-chromium@1.5.334: {} - es-module-lexer@1.7.0: {} + emoji-regex@10.6.0: {} - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 + emoji-regex@8.0.0: {} - es-set-tostringtag@2.1.0: + end-of-stream@1.4.5: dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - escalade@3.2.0: {} + once: 1.4.0 - escape-html@1.0.3: {} + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.2 - escape-string-regexp@1.0.5: {} + env-paths@2.2.1: {} - escape-string-regexp@2.0.0: {} + environment@1.1.0: {} - escape-string-regexp@4.0.0: {} + error-causes@3.0.2: {} - eslint-config-prettier@9.1.2(eslint@8.57.1): + error-ex@1.3.4: dependencies: - eslint: 8.57.1 + is-arrayish: 0.2.1 - eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.2): - dependencies: - eslint: 8.57.1 - prettier: 3.8.2 - prettier-linter-helpers: 1.0.1 - synckit: 0.11.12 + es-module-lexer@2.0.0: {} + + esbuild@0.18.20: optionalDependencies: - '@types/eslint': 9.6.1 - eslint-config-prettier: 9.1.2(eslint@8.57.1) + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} eslint-scope@5.1.1: dependencies: @@ -4329,80 +7257,24 @@ snapshots: estraverse@5.3.0: {} - esutils@2.0.3: {} - - etag@1.8.1: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 - events@3.3.0: {} + esutils@2.0.3: {} - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + event-target-shim@5.0.1: {} - exit@0.1.2: {} + eventemitter3@5.0.4: {} - expect@29.7.0: - dependencies: - '@jest/expect-utils': 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 + events@3.3.0: {} - express@4.22.1: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.4 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.0.7 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.2 - fresh: 0.5.2 - http-errors: 2.0.1 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.13 - proxy-addr: 2.0.7 - qs: 6.14.2 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.2 - serve-static: 1.16.3 - setprototypeof: 1.2.0 - statuses: 2.0.2 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color + expect-type@1.3.0: {} - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 + fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} - fast-diff@1.3.0: {} - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4413,59 +7285,89 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-json-stringify@6.3.0: + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + fast-uri: 3.1.0 + json-schema-ref-resolver: 3.0.0 + rfdc: 1.4.1 + fast-levenshtein@2.0.6: {} + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + fast-safe-stringify@2.1.1: {} fast-uri@3.1.0: {} - fastq@1.20.1: + fast-xml-builder@1.1.4: dependencies: - reusify: 1.1.0 + path-expression-matcher: 1.5.0 - fb-watchman@2.0.2: + fast-xml-parser@5.5.8: dependencies: - bser: 2.1.1 + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.5.0 + strnum: 2.2.3 - fflate@0.8.2: {} + fastify-plugin@5.1.0: {} + + fastify@5.8.4: + dependencies: + '@fastify/ajv-compiler': 4.0.5 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.0.3 + '@fastify/proxy-addr': 5.1.0 + abstract-logging: 2.0.1 + avvio: 9.2.0 + fast-json-stringify: 6.3.0 + find-my-way: 9.5.0 + light-my-request: 6.6.0 + pino: 10.3.1 + process-warning: 5.0.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.7.4 + toad-cache: 3.7.0 - figures@3.2.0: + fastq@1.20.1: dependencies: - escape-string-regexp: 1.0.5 + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 - file-type@20.4.1: + file-type@21.3.4: dependencies: - '@tokenizer/inflate': 0.2.7 + '@tokenizer/inflate': 0.4.1 strtok3: 10.3.5 token-types: 6.1.2 uint8array-extras: 1.5.0 transitivePeerDependencies: - supports-color - fill-range@7.1.1: + filelist@1.0.6: dependencies: - to-regex-range: 5.0.1 + minimatch: 5.1.9 - finalhandler@1.3.2: + fill-range@7.1.1: 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.2 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color + to-regex-range: 5.0.1 - find-up@4.1.0: + find-my-way@9.5.0: dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.0 find-up@5.0.0: dependencies: @@ -4480,46 +7382,22 @@ snapshots: flatted@3.4.2: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)): dependencies: '@babel/code-frame': 7.29.0 chalk: 4.1.2 - chokidar: 3.6.0 - cosmiconfig: 8.3.6(typescript@5.7.2) + chokidar: 4.0.3 + cosmiconfig: 8.3.6(typescript@5.9.3) deepmerge: 4.3.1 fs-extra: 10.1.0 memfs: 3.5.3 - minimatch: 3.1.5 - node-abort-controller: 3.1.1 - schema-utils: 3.3.0 - semver: 7.7.4 - tapable: 2.3.2 - typescript: 5.7.2 - webpack: 5.97.1 - - form-data@4.0.5: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - - formidable@2.1.5: - dependencies: - '@paralleldrive/cuid2': 2.3.1 - dezalgo: 1.0.4 - once: 1.4.0 - qs: 6.15.1 - - forwarded@0.2.0: {} - - fresh@0.5.2: {} + minimatch: 3.1.5 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.7.4 + tapable: 2.3.2 + typescript: 5.9.3 + webpack: 5.106.0(@swc/core@1.15.24)(esbuild@0.27.7) fs-extra@10.1.0: dependencies: @@ -4534,33 +7412,21 @@ snapshots: fsevents@2.3.3: optional: true - function-bind@1.1.2: {} - - gensync@1.0.0-beta.2: {} - get-caller-file@2.0.5: {} - get-intrinsic@1.3.0: - 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 - - get-package-type@0.1.0: {} + get-east-asian-width@1.5.0: {} - get-proto@1.0.1: + get-tsconfig@4.13.7: dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + resolve-pkg-maps: 1.0.0 - get-stream@6.0.1: {} + git-raw-commits@5.0.1(conventional-commits-parser@6.4.0): + dependencies: + '@conventional-changelog/git-client': 2.7.0(conventional-commits-parser@6.4.0) + meow: 13.2.0 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser glob-parent@5.1.2: dependencies: @@ -4572,14 +7438,11 @@ snapshots: glob-to-regexp@0.4.1: {} - glob@10.4.5: + glob@13.0.6: dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 + minimatch: 10.2.5 minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 + path-scurry: 2.0.2 glob@7.2.3: dependencies: @@ -4590,6 +7453,10 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -4603,8 +7470,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - gopd@1.2.0: {} - graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -4620,22 +7485,6 @@ snapshots: has-flag@4.0.0: {} - has-own-prop@2.0.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - html-escaper@2.0.2: {} http-errors@2.0.1: @@ -4646,9 +7495,9 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - human-signals@2.1.0: {} + husky@9.1.7: {} - iconv-lite@0.4.24: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -4661,10 +7510,7 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-local@3.2.0: - dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 + import-meta-resolve@4.2.0: {} imurmurhash@0.1.4: {} @@ -4675,59 +7521,33 @@ snapshots: inherits@2.0.4: {} - inquirer@8.2.6: - dependencies: - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - external-editor: 3.1.0 - figures: 3.2.0 - lodash: 4.18.1 - mute-stream: 0.0.8 - ora: 5.4.1 - run-async: 2.4.1 - rxjs: 7.8.2 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - wrap-ansi: 6.2.0 + ini@4.1.1: {} - inquirer@9.2.15: + ioredis@5.10.1: dependencies: - '@ljharb/through': 2.3.14 - ansi-escapes: 4.3.2 - chalk: 5.6.2 - cli-cursor: 3.1.0 - cli-width: 4.1.0 - external-editor: 3.1.0 - figures: 3.2.0 - lodash: 4.18.1 - mute-stream: 1.0.0 - ora: 5.4.1 - run-async: 3.0.0 - rxjs: 7.8.2 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 + '@ioredis/commands': 1.5.1 + cluster-key-slot: 1.1.2 + debug: 4.4.3 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color - ipaddr.js@1.9.1: {} + ipaddr.js@2.3.0: {} is-arrayish@0.2.1: {} - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} - is-generator-fn@2.1.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.5.0 is-glob@4.0.3: dependencies: @@ -4737,50 +7557,28 @@ snapshots: is-number@7.0.0: {} + is-obj@2.0.0: {} + is-path-inside@3.0.3: {} - is-stream@2.0.1: {} + is-plain-obj@4.1.0: {} + + is-standalone-pwa@0.1.1: {} is-unicode-supported@0.1.0: {} + isarray@1.0.0: {} + isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} - istanbul-lib-instrument@5.2.1: - dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - istanbul-lib-instrument@6.0.3: - dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 7.7.4 - transitivePeerDependencies: - - supports-color - istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@4.0.1: - dependencies: - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 @@ -4788,391 +7586,200 @@ snapshots: iterare@1.2.1: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jest-changed-files@29.7.0: - dependencies: - execa: 5.1.1 - jest-util: 29.7.0 - p-limit: 3.1.0 - - jest-circus@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - chalk: 4.1.2 - co: 4.6.0 - dedent: 1.7.2 - is-generator-fn: 2.1.0 - jest-each: 29.7.0 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - p-limit: 3.1.0 - pretty-format: 29.7.0 - pure-rand: 6.1.0 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-cli@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): + jake@10.9.4: dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node + async: 3.2.6 + filelist: 1.0.6 + picocolors: 1.1.1 - jest-config@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): + jest-worker@27.5.1: dependencies: - '@babel/core': 7.29.0 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.0) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: '@types/node': 20.19.39 - ts-node: 10.9.2(@types/node@20.19.39)(typescript@5.9.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-diff@29.7.0: - dependencies: - chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-docblock@29.7.0: - dependencies: - detect-newline: 3.1.0 - - jest-each@29.7.0: - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - jest-get-type: 29.6.3 - jest-util: 29.7.0 - pretty-format: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 - jest-environment-node@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - jest-mock: 29.7.0 - jest-util: 29.7.0 + jiti@2.6.1: {} - jest-get-type@29.6.3: {} + js-tokens@10.0.0: {} - jest-haste-map@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.9 - '@types/node': 20.19.39 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - jest-worker: 29.7.0 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 + js-tokens@4.0.0: {} - jest-leak-detector@29.7.0: + js-yaml@4.1.1: dependencies: - jest-get-type: 29.6.3 - pretty-format: 29.7.0 + argparse: 2.0.1 - jest-matcher-utils@29.7.0: - dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 + json-buffer@3.0.1: {} - jest-message-util@29.7.0: - dependencies: - '@babel/code-frame': 7.29.0 - '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - stack-utils: 2.0.6 + json-parse-even-better-errors@2.3.1: {} - jest-mock@29.7.0: + json-schema-ref-resolver@3.0.0: dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - jest-util: 29.7.0 + dequal: 2.0.3 - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - optionalDependencies: - jest-resolve: 29.7.0 + json-schema-traverse@0.4.1: {} - jest-regex-util@29.6.3: {} + json-schema-traverse@1.0.0: {} - jest-resolve-dependencies@29.7.0: - dependencies: - jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color + json-stable-stringify-without-jsonify@1.0.1: {} - jest-resolve@29.7.0: - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) - jest-util: 29.7.0 - jest-validate: 29.7.0 - resolve: 1.22.11 - resolve.exports: 2.0.3 - slash: 3.0.0 + json5@2.2.3: {} - jest-runner@29.7.0: - dependencies: - '@jest/console': 29.7.0 - '@jest/environment': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - chalk: 4.1.2 - emittery: 0.13.1 - graceful-fs: 4.2.11 - jest-docblock: 29.7.0 - jest-environment-node: 29.7.0 - jest-haste-map: 29.7.0 - jest-leak-detector: 29.7.0 - jest-message-util: 29.7.0 - jest-resolve: 29.7.0 - jest-runtime: 29.7.0 - jest-util: 29.7.0 - jest-watcher: 29.7.0 - jest-worker: 29.7.0 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color + jsonc-parser@3.3.1: {} - jest-runtime@29.7.0: + jsonfile@6.2.0: dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/globals': 29.7.0 - '@jest/source-map': 29.6.3 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - chalk: 4.1.2 - cjs-module-lexer: 1.4.3 - collect-v8-coverage: 1.0.3 - glob: 7.2.3 + universalify: 2.0.1 + optionalDependencies: graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - jest-snapshot@29.7.0: + jsonwebtoken@9.0.3: dependencies: - '@babel/core': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) - '@babel/types': 7.29.0 - '@jest/expect-utils': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) - chalk: 4.1.2 - expect: 29.7.0 - graceful-fs: 4.2.11 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - natural-compare: 1.4.0 - pretty-format: 29.7.0 + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 semver: 7.7.4 - transitivePeerDependencies: - - supports-color - - jest-util@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - chalk: 4.1.2 - ci-info: 3.9.0 - graceful-fs: 4.2.11 - picomatch: 2.3.2 - jest-validate@29.7.0: + jwa@2.0.1: dependencies: - '@jest/types': 29.6.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.6.3 - leven: 3.1.0 - pretty-format: 29.7.0 + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 - jest-watcher@29.7.0: + jws@4.0.1: dependencies: - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.19.39 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 29.7.0 - string-length: 4.0.2 + jwa: 2.0.1 + safe-buffer: 5.2.1 - jest-worker@27.5.1: + keyv@4.5.4: dependencies: - '@types/node': 20.19.39 - merge-stream: 2.0.0 - supports-color: 8.1.1 + json-buffer: 3.0.1 - jest-worker@29.7.0: + levn@0.4.1: dependencies: - '@types/node': 20.19.39 - jest-util: 29.7.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 - jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): + light-my-request@6.6.0: dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - js-tokens@4.0.0: {} + cookie: 1.1.1 + process-warning: 4.0.1 + set-cookie-parser: 2.7.2 - js-yaml@3.14.2: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 + lightningcss-android-arm64@1.32.0: + optional: true - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 + lightningcss-darwin-arm64@1.32.0: + optional: true - jsesc@3.1.0: {} + lightningcss-darwin-x64@1.32.0: + optional: true - json-buffer@3.0.1: {} + lightningcss-freebsd-x64@1.32.0: + optional: true - json-parse-even-better-errors@2.3.1: {} + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true - json-schema-traverse@0.4.1: {} + lightningcss-linux-arm64-gnu@1.32.0: + optional: true - json-schema-traverse@1.0.0: {} + lightningcss-linux-arm64-musl@1.32.0: + optional: true - json-stable-stringify-without-jsonify@1.0.1: {} + lightningcss-linux-x64-gnu@1.32.0: + optional: true - json5@2.2.3: {} + lightningcss-linux-x64-musl@1.32.0: + optional: true - jsonc-parser@3.2.1: {} + lightningcss-win32-arm64-msvc@1.32.0: + optional: true - jsonc-parser@3.3.1: {} + lightningcss-win32-x64-msvc@1.32.0: + optional: true - jsonfile@6.2.0: + lightningcss@1.32.0: dependencies: - universalify: 2.0.1 + detect-libc: 2.1.2 optionalDependencies: - graceful-fs: 4.2.11 - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 - kleur@3.0.3: {} + lines-and-columns@1.2.4: {} - leven@3.1.0: {} + lint-staged@16.4.0: + dependencies: + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.4 + string-argv: 0.3.2 + tinyexec: 1.1.1 + yaml: 2.8.3 - levn@0.4.1: + listr2@9.0.5: dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 + cli-truncate: 5.2.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 - lines-and-columns@1.2.4: {} + load-esm@1.0.3: {} loader-runner@4.3.1: {} - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 - lodash.memoize@4.1.2: {} + lodash.camelcase@4.3.0: {} + + lodash.defaults@4.2.0: {} + + lodash.includes@4.3.0: {} + + lodash.isarguments@3.1.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.kebabcase@4.1.1: {} lodash.merge@4.6.2: {} + lodash.mergewith@4.6.2: {} + + lodash.once@4.1.1: {} + + lodash.snakecase@4.1.1: {} + + lodash.startcase@4.4.0: {} + + lodash.upperfirst@4.3.1: {} + lodash@4.18.1: {} log-symbols@4.1.0: @@ -5180,42 +7787,46 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 - lru-cache@10.4.3: {} + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + lru-cache@11.3.3: {} - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 + luxon@3.7.2: {} - magic-string@0.30.8: + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - make-dir@4.0.0: + magic-string@0.30.21: dependencies: - semver: 7.7.4 - - make-error@1.3.6: {} + '@jridgewell/sourcemap-codec': 1.5.5 - makeerror@1.0.12: + magicast@0.5.2: dependencies: - tmpl: 1.0.5 - - math-intrinsics@1.1.0: {} + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 - media-typer@0.3.0: {} + make-dir@4.0.0: + dependencies: + semver: 7.7.4 memfs@3.5.3: dependencies: fs-monkey: 1.1.0 - merge-descriptors@1.0.3: {} + meow@13.2.0: {} merge-stream@2.0.0: {} merge2@1.4.1: {} - methods@1.1.2: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -5227,21 +7838,25 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@1.6.0: {} - - mime@2.6.0: {} + mime@3.0.0: {} mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.5: dependencies: brace-expansion: 1.1.13 - minimatch@9.0.3: + minimatch@5.1.9: dependencies: brace-expansion: 2.0.3 - minimatch@9.0.9: + minimatch@9.0.3: dependencies: brace-expansion: 2.0.3 @@ -5249,61 +7864,63 @@ snapshots: minipass@7.1.3: {} - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - - ms@2.0.0: {} - ms@2.1.3: {} - multer@2.0.2: + msgpackr-extract@3.0.3: dependencies: - append-field: 1.0.0 - busboy: 1.6.0 - concat-stream: 2.0.0 - mkdirp: 0.5.6 - object-assign: 4.1.1 - type-is: 1.6.18 - xtend: 4.0.2 + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.5: + optionalDependencies: + msgpackr-extract: 3.0.3 - mute-stream@0.0.8: {} + mute-stream@2.0.0: {} - mute-stream@1.0.0: {} + nanoid@3.3.11: {} natural-compare@1.4.0: {} - negotiator@0.6.3: {} - neo-async@2.6.2: {} + nestjs-zod@5.3.0(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6): + dependencies: + '@nestjs/common': 11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2) + deepmerge: 4.3.1 + rxjs: 7.8.2 + zod: 4.3.6 + optionalDependencies: + '@nestjs/swagger': 11.2.7(@fastify/static@9.1.0)(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) + node-abort-controller@3.1.1: {} + node-addon-api@8.7.0: {} + node-emoji@1.11.0: dependencies: lodash: 4.18.1 - node-fetch@2.7.0: + node-gyp-build-optional-packages@5.2.2: dependencies: - whatwg-url: 5.0.0 + detect-libc: 2.1.2 + optional: true - node-int64@0.4.0: {} + node-gyp-build@4.8.4: {} node-releases@2.0.37: {} - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 + nodemailer@8.0.5: {} - object-assign@4.1.1: {} + obug@2.1.1: {} - object-inspect@1.13.4: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 + on-exit-leak-free@2.1.2: {} once@1.4.0: dependencies: @@ -5313,6 +7930,10 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5334,28 +7955,23 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - os-tmpdir@1.0.2: {} - - p-limit@2.3.0: + otplib@13.4.0: dependencies: - p-try: 2.2.0 + '@otplib/core': 13.4.0 + '@otplib/hotp': 13.4.0 + '@otplib/plugin-base32-scure': 13.4.0 + '@otplib/plugin-crypto-noble': 13.4.0 + '@otplib/totp': 13.4.0 + '@otplib/uri': 13.4.0 p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - p-locate@5.0.0: dependencies: p-limit: 3.1.0 - p-try@2.2.0: {} - - package-json-from-dist@1.0.1: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -5367,89 +7983,168 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parseurl@1.3.3: {} + passport-jwt@4.0.1: + dependencies: + jsonwebtoken: 9.0.3 + passport-strategy: 1.0.0 + + passport-strategy@1.0.0: {} + + passport@0.7.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 path-exists@4.0.0: {} + path-expression-matcher@1.5.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} - path-parse@1.0.7: {} - - path-scurry@1.11.1: + path-scurry@2.0.2: dependencies: - lru-cache: 10.4.3 + lru-cache: 11.3.3 minipass: 7.1.3 - path-to-regexp@0.1.13: {} - - path-to-regexp@3.3.0: {} + path-to-regexp@8.4.2: {} path-type@4.0.0: {} + pathe@2.0.3: {} + + pause@0.0.1: {} + + peek-stream@1.1.3: + dependencies: + buffer-from: 1.1.2 + duplexify: 3.7.1 + through2: 2.0.5 + + pg-cloudflare@1.3.0: + optional: true + + pg-connection-string@2.12.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.13.0(pg@8.20.0): + dependencies: + pg: 8.20.0 + + pg-protocol@1.13.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.20.0: + dependencies: + pg-connection-string: 2.12.0 + pg-pool: 3.13.0(pg@8.20.0) + pg-protocol: 1.13.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.3.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.1: {} picomatch@2.3.2: {} - picomatch@4.0.1: {} + picomatch@4.0.4: {} + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 - pirates@4.0.7: {} + pino-std-serializers@7.1.0: {} - pkg-dir@4.2.0: + pino@10.3.1: dependencies: - find-up: 4.1.0 + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 pluralize@8.0.0: {} - prelude-ls@1.2.1: {} + postcss@8.5.9: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postgres-array@2.0.0: {} - prettier-linter-helpers@1.0.1: + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: dependencies: - fast-diff: 1.3.0 + xtend: 4.0.2 + + prelude-ls@1.2.1: {} prettier@3.8.2: {} - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 + process-nextick-args@2.0.1: {} - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 + process-warning@4.0.1: {} - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 + process-warning@5.0.0: {} - punycode@2.3.1: {} + process@0.11.10: {} - pure-rand@6.1.0: {} + prom-client@15.1.3: + dependencies: + '@opentelemetry/api': 1.9.1 + tdigest: 0.1.2 - qs@6.14.2: + pump@3.0.4: dependencies: - side-channel: 1.1.0 + end-of-stream: 1.4.5 + once: 1.4.0 - qs@6.15.1: + pumpify@2.0.1: dependencies: - side-channel: 1.1.0 + duplexify: 4.1.3 + inherits: 2.0.4 + pump: 3.0.4 + + punycode@2.3.1: {} queue-microtask@1.2.3: {} - range-parser@1.2.1: {} + quick-format-unescaped@4.0.4: {} - raw-body@2.5.3: + readable-stream@2.3.8: dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - - react-is@18.3.1: {} + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 readable-stream@3.6.2: dependencies: @@ -5457,48 +8152,80 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@3.6.0: + readable-stream@4.7.0: dependencies: - picomatch: 2.3.2 + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 - reflect-metadata@0.2.2: {} + readdirp@4.1.2: {} - repeat-string@1.6.1: {} + real-require@0.2.0: {} - require-directory@2.1.1: {} + redis-errors@1.2.0: {} - require-from-string@2.0.2: {} + redis-info@3.1.0: + dependencies: + lodash: 4.18.1 - resolve-cwd@3.0.0: + redis-parser@3.0.0: dependencies: - resolve-from: 5.0.0 + redis-errors: 1.2.0 + + reflect-metadata@0.2.2: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} resolve-from@4.0.0: {} resolve-from@5.0.0: {} - resolve.exports@2.0.3: {} - - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 + resolve-pkg-maps@1.0.0: {} restore-cursor@3.1.0: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + ret@0.5.0: {} + reusify@1.1.0: {} + rfdc@1.4.1: {} + rimraf@3.0.2: dependencies: glob: 7.2.3 - run-async@2.4.1: {} - - run-async@3.0.0: {} + rolldown@1.0.0-rc.15: + dependencies: + '@oxc-project/types': 0.124.0 + '@rolldown/pluginutils': 1.0.0-rc.15 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-x64': 1.0.0-rc.15 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 run-parallel@1.2.0: dependencies: @@ -5512,8 +8239,16 @@ snapshots: dependencies: tslib: 2.8.1 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} + safe-regex2@5.1.0: + dependencies: + ret: 0.5.0 + + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} schema-utils@3.3.0: @@ -5529,45 +8264,11 @@ snapshots: ajv-formats: 2.1.1(ajv@8.18.0) ajv-keywords: 5.1.0(ajv@8.18.0) - semver@6.3.1: {} + secure-json-parse@4.1.0: {} semver@7.7.4: {} - send@0.19.2: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.1 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@1.16.3: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.2 - transitivePeerDependencies: - - supports-color - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 + set-cookie-parser@2.7.2: {} setprototypeof@1.2.0: {} @@ -5577,46 +8278,29 @@ snapshots: shebang-regex@3.0.0: {} - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} signal-exit@4.1.0: {} - sisteransi@1.0.5: {} - slash@3.0.0: {} - source-map-support@0.5.13: + slice-ansi@7.1.2: dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.1: {} source-map-support@0.5.21: dependencies: @@ -5629,20 +8313,19 @@ snapshots: source-map@0.7.6: {} - sprintf-js@1.0.3: {} + split2@4.2.0: {} - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 + stackback@0.0.2: {} + + standard-as-callback@2.1.0: {} statuses@2.0.2: {} - streamsearch@1.1.0: {} + std-env@4.0.0: {} - string-length@4.0.2: - dependencies: - char-regex: 1.0.2 - strip-ansi: 6.0.1 + stream-shift@1.0.3: {} + + string-argv@0.3.2: {} string-width@4.2.3: dependencies: @@ -5650,12 +8333,21 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + string-width@8.2.0: dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 + get-east-asian-width: 1.5.0 strip-ansi: 7.2.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -5670,38 +8362,14 @@ snapshots: strip-bom@3.0.0: {} - strip-bom@4.0.0: {} - - strip-final-newline@2.0.0: {} - strip-json-comments@3.1.1: {} + strnum@2.2.3: {} + strtok3@10.3.5: dependencies: '@tokenizer/token': 0.3.0 - superagent@8.1.2: - dependencies: - component-emitter: 1.3.1 - cookiejar: 2.1.4 - debug: 4.4.3 - fast-safe-stringify: 2.1.1 - form-data: 4.0.5 - formidable: 2.1.5 - methods: 1.1.2 - mime: 2.6.0 - qs: 6.15.1 - semver: 7.7.4 - transitivePeerDependencies: - - supports-color - - supertest@6.3.4: - dependencies: - methods: 1.1.2 - superagent: 8.1.2 - transitivePeerDependencies: - - supports-color - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -5710,23 +8378,28 @@ snapshots: dependencies: has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} + swagger-ui-dist@5.32.2: + dependencies: + '@scarf/scarf': 1.4.0 symbol-observable@4.0.0: {} - synckit@0.11.12: - dependencies: - '@pkgr/core': 0.2.9 - tapable@2.3.2: {} - terser-webpack-plugin@5.4.0(webpack@5.97.1): + tdigest@0.1.2: + dependencies: + bintrees: 1.0.2 + + terser-webpack-plugin@5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.97.1 + webpack: 5.106.0(@swc/core@1.15.24)(esbuild@0.27.7) + optionalDependencies: + '@swc/core': 1.15.24 + esbuild: 0.27.7 terser@5.46.1: dependencies: @@ -5735,26 +8408,34 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - test-exclude@6.0.0: + text-table@0.2.0: {} + + thread-stream@4.0.0: dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.5 + real-require: 0.2.0 - text-table@0.2.0: {} + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + tinybench@2.9.0: {} - through@2.3.8: {} + tinyexec@1.1.1: {} - tmp@0.0.33: + tinyglobby@0.2.16: dependencies: - os-tmpdir: 1.0.2 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 - tmpl@1.0.5: {} + tinyrainbow@3.1.0: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + toad-cache@3.7.0: {} + toidentifier@1.0.1: {} token-types@6.1.2: @@ -5763,35 +8444,13 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - tr46@0.0.3: {} - - tree-kill@1.2.2: {} + transliteration@2.6.1: {} ts-api-utils@1.4.3(typescript@5.9.3): dependencies: typescript: 5.9.3 - ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3): - dependencies: - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - handlebars: 4.7.9 - jest: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.7.4 - type-fest: 4.41.0 - typescript: 5.9.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.29.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.0) - jest-util: 29.7.0 - - ts-loader@9.5.7(typescript@5.9.3)(webpack@5.97.1): + ts-loader@9.5.7(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)): dependencies: chalk: 4.1.2 enhanced-resolve: 5.20.1 @@ -5799,25 +8458,7 @@ snapshots: semver: 7.7.4 source-map: 0.7.6 typescript: 5.9.3 - webpack: 5.97.1 - - ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.39 - acorn: 8.16.0 - acorn-walk: 8.3.5 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 5.9.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 + webpack: 5.106.0(@swc/core@1.15.24)(esbuild@0.27.7) tsconfig-paths-webpack-plugin@4.2.0: dependencies: @@ -5834,28 +8475,28 @@ snapshots: tslib@2.8.1: {} + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.13.7 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - type-detect@4.0.8: {} - type-fest@0.20.2: {} - type-fest@0.21.3: {} + typescript@5.9.3: {} - type-fest@4.41.0: {} + ua-is-frozen@0.1.2: {} - type-is@1.6.18: + ua-parser-js@2.0.9: dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - - typedarray@0.0.6: {} - - typescript@5.7.2: {} - - typescript@5.9.3: {} + detect-europe-js: 0.1.2 + is-standalone-pwa: 0.1.1 + ua-is-frozen: 0.1.2 uglify-js@3.19.3: optional: true @@ -5870,8 +8511,6 @@ snapshots: universalify@2.0.1: {} - unpipe@1.0.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -5886,19 +8525,52 @@ snapshots: utils-merge@1.0.1: {} - v8-compile-cache-lib@3.0.1: {} - - v8-to-istanbul@9.3.0: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@types/istanbul-lib-coverage': 2.0.6 - convert-source-map: 2.0.0 - - vary@1.1.2: {} + uuid@11.1.0: {} - walker@1.0.8: + vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: - makeerror: 1.0.12 + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.9 + rolldown: 1.0.0-rc.15 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 20.19.39 + esbuild: 0.27.7 + fsevents: 2.3.3 + jiti: 2.6.1 + terser: 5.46.1 + tsx: 4.21.0 + yaml: 2.8.3 + + vitest@4.1.4(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/coverage-v8@4.1.4)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 20.19.39 + '@vitest/coverage-v8': 4.1.4(vitest@4.1.4) + transitivePeerDependencies: + - msw watchpack@2.5.1: dependencies: @@ -5909,24 +8581,24 @@ snapshots: dependencies: defaults: 1.0.4 - webidl-conversions@3.0.1: {} - webpack-node-externals@3.0.0: {} webpack-sources@3.3.4: {} - webpack@5.97.1: + webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 enhanced-resolve: 5.20.1 - es-module-lexer: 1.7.0 + es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -5935,9 +8607,9 @@ snapshots: loader-runner: 4.3.1 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 4.3.3 tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(webpack@5.97.1) + terser-webpack-plugin: 5.4.0(@swc/core@1.15.24)(esbuild@0.27.7)(webpack@5.106.0(@swc/core@1.15.24)(esbuild@0.27.7)) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: @@ -5945,15 +8617,20 @@ snapshots: - esbuild - uglify-js - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + optional: true + word-wrap@1.2.5: {} wordwrap@1.0.0: {} @@ -5970,24 +8647,19 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 - string-width: 5.1.2 + string-width: 7.2.0 strip-ansi: 7.2.0 wrappy@1.0.2: {} - write-file-atomic@4.0.2: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - xtend@4.0.2: {} y18n@5.0.8: {} - yallist@3.1.1: {} + yaml@2.8.3: {} yargs-parser@21.1.1: {} @@ -6001,6 +8673,8 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yn@3.1.1: {} - yocto-queue@0.1.0: {} + + yoctocolors-cjs@2.1.3: {} + + zod@4.3.6: {} diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts deleted file mode 100644 index d22f389..0000000 --- a/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - - appController = app.get(AppController); - }); - - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); - }); -}); diff --git a/src/app.controller.ts b/src/app.controller.ts deleted file mode 100644 index cce879e..0000000 --- a/src/app.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Get() - getHello(): string { - return this.appService.getHello(); - } -} diff --git a/src/app.module.ts b/src/app.module.ts index 8662803..404fdf8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,10 +1,76 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; +import { ConfigModule } from '@libs/config'; +import { DatabaseModule } from '@libs/database'; +import { ConfigService } from '@nestjs/config'; +import * as schema from './shared/entities'; +import { APP_FILTER, APP_PIPE } from '@nestjs/core'; +import { ZodValidationPipe } from 'nestjs-zod'; +import { PrometheusModule } from '@willsoto/nestjs-prometheus'; +import { HealthModule } from '@libs/health'; +import { UserModule } from './user'; +import { GlobalExceptionFilter } from '@shared/error'; +import { AuthModule } from './auth/auth.module'; +import { BullBoardModule } from '@bull-board/nestjs'; +import { FastifyAdapter } from '@bull-board/fastify'; +import { BullModule } from '@nestjs/bullmq'; +import { MailModule } from '@shared/adapters/mail'; +import { MigrationService } from '@shared/migration'; +import { TeamsModule } from './modules/teams'; +import { ProjectsModule } from './modules/projects'; @Module({ - imports: [], - controllers: [AppController], - providers: [AppService], + imports: [ + ConfigModule, + PrometheusModule.registerAsync({ + useFactory: () => ({ + path: 'dump', + defaultMetrics: { + enabled: process.env.NODE_ENV !== 'test', + }, + }), + }), + DatabaseModule.registerAsync({ + global: true, + inject: [ConfigService], + useFactory: (cfg: ConfigService) => { + return { + schema, + schemaName: cfg.getOrThrow('DB_SCHEMA'), + logging: true, + }; + }, + }), + BullModule.forRootAsync({ + inject: [ConfigService], + useFactory: (cfg: ConfigService) => ({ + connection: { + password: cfg.get('REDIS_PASSWORD'), + host: cfg.getOrThrow('REDIS_HOST'), + port: cfg.get('REDIS_PORT'), + }, + }), + }), + AuthModule, + UserModule, + TeamsModule, + ProjectsModule, + BullBoardModule.forRoot({ + route: '/queues', + adapter: FastifyAdapter, + }), + HealthModule.register('gateway'), + ], + providers: [ + MigrationService, + MailModule, + { + provide: APP_PIPE, + useClass: ZodValidationPipe, + }, + { + provide: APP_FILTER, + useClass: GlobalExceptionFilter, + }, + ], }) export class AppModule {} diff --git a/src/app.service.ts b/src/app.service.ts deleted file mode 100644 index 927d7cc..0000000 --- a/src/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getHello(): string { - return 'Hello World!'; - } -} diff --git a/src/auth/application/auth.facade.ts b/src/auth/application/auth.facade.ts new file mode 100644 index 0000000..e2aac1f --- /dev/null +++ b/src/auth/application/auth.facade.ts @@ -0,0 +1,66 @@ +import { Injectable } from '@nestjs/common'; +import { + SignInUseCase, + SignUpUseCase, + SignOutUseCase, + SignUpVerifyUseCase, + RefreshTokensUseCase, + ResetPasswordUseCase, + VerifyResetPasswordUseCase, + ConfirmResetPasswordUseCase, +} from './use-cases'; +import { + PasswordResetConfirmDto, + ResetPasswordDto, + SignInDto, + SignUpDto, + VerifyDto, + VerifyResetCodeDto, +} from './dtos'; +import type { DeviceMetadata } from '../infrastructure/utils/get-device-meta'; + +@Injectable() +export class AuthFacade { + constructor( + private readonly signInUseCase: SignInUseCase, + private readonly signUpUseCase: SignUpUseCase, + private readonly signOutUseCase: SignOutUseCase, + private readonly signUpVerifyUseCase: SignUpVerifyUseCase, + private readonly refreshTokensUseCase: RefreshTokensUseCase, + private readonly resetPasswordUseCase: ResetPasswordUseCase, + private readonly verifyResetPasswordUseCase: VerifyResetPasswordUseCase, + private readonly confirmResetPasswordUseCase: ConfirmResetPasswordUseCase, + ) {} + + async signIn(dto: SignInDto, device: DeviceMetadata) { + return this.signInUseCase.execute(dto, device); + } + + async signUp(dto: SignUpDto) { + return this.signUpUseCase.execute(dto); + } + + async verifySignUp(dto: VerifyDto, device: DeviceMetadata) { + return this.signUpVerifyUseCase.execute(dto, device); + } + + async signOut(userId: string) { + return this.signOutUseCase.execute(userId); + } + + async refreshTokens(token: string, device: DeviceMetadata) { + return this.refreshTokensUseCase.execute(token, device); + } + + async sendResetCode(dto: ResetPasswordDto) { + return this.resetPasswordUseCase.execute(dto); + } + + async verifyResetCode(dto: VerifyResetCodeDto) { + return this.verifyResetPasswordUseCase.execute(dto); + } + + async confirmNewPassword(dto: PasswordResetConfirmDto) { + return this.confirmResetPasswordUseCase.execute(dto); + } +} diff --git a/src/auth/application/controller/auth/controller.ts b/src/auth/application/controller/auth/controller.ts new file mode 100644 index 0000000..ec136be --- /dev/null +++ b/src/auth/application/controller/auth/controller.ts @@ -0,0 +1,98 @@ +import { ApiBaseController } from '@shared/decorators'; +import { Body, HttpCode, Post, Req, Res, UseGuards } from '@nestjs/common'; +import { + PostLoginSwagger, + PostLogoutSwagger, + PostRefreshSwagger, + PostRegisterSwagger, + PostSignUpConfirmSwagger, +} from './swagger'; +import { SignInDto, SignUpDto, VerifyDto } from '../../dtos'; +import type { FastifyReply, FastifyRequest } from 'fastify'; +import { BearerAuthGuard, CookieAuthGuard } from '@shared/guards'; +import { AuthFacade } from '../../auth.facade'; +import { getDeviceMeta } from '@core/auth/infrastructure/utils/get-device-meta'; + +@ApiBaseController('auth', 'Auth') +export class AuthController { + constructor(private readonly facade: AuthFacade) {} + + @Post('sign-up') + @PostRegisterSwagger() + @HttpCode(202) + async signUp(@Body() dto: SignUpDto) { + return this.facade.signUp(dto); + } + + @Post('sign-up/confirm') + @PostSignUpConfirmSwagger() + @HttpCode(201) + async verifySignUp( + @Res({ passthrough: true }) res: FastifyReply, + @Req() req: FastifyRequest, + @Body() dto: VerifyDto, + ) { + const meta = getDeviceMeta(req); + const { tokens, ...response } = await this.facade.verifySignUp(dto, meta); + + res.setCookie('refresh', tokens.refresh, { + httpOnly: true, + secure: false, + path: '/', + sameSite: 'lax', + }); + + return { ...response, token: tokens.access }; + } + + @Post('sign-in') + @PostLoginSwagger() + async signIn( + @Res({ passthrough: true }) res: FastifyReply, + @Req() req: FastifyRequest, + @Body() dto: SignInDto, + ) { + const meta = getDeviceMeta(req); + const { tokens, ...response } = await this.facade.signIn(dto, meta); + + res.setCookie('refresh', tokens.refresh, { + httpOnly: true, + secure: false, + path: '/', + sameSite: 'lax', + }); + + return { ...response, token: tokens.access }; + } + + @Post('sign-out') + @UseGuards(BearerAuthGuard) + @PostLogoutSwagger() + async logout(@Res({ passthrough: true }) res: FastifyReply, @Req() req: FastifyRequest) { + const session = req.cookies?.['refresh']; + const response = await this.facade.signOut(session); + + res.clearCookie('refresh', { path: '/' }); + + return response; + } + + @Post('refresh') + @UseGuards(CookieAuthGuard) + @PostRefreshSwagger() + @HttpCode(200) + async refresh(@Res({ passthrough: true }) res: FastifyReply, @Req() req: FastifyRequest) { + const meta = getDeviceMeta(req); + const session = req.cookies?.['refresh']; + const { tokens, ...response } = await this.facade.refreshTokens(session, meta); + + res.setCookie('refresh', tokens.refresh, { + httpOnly: true, + secure: false, + path: '/', + sameSite: 'lax', + }); + + return { token: tokens.access, ...response }; + } +} diff --git a/src/auth/application/controller/auth/swagger.ts b/src/auth/application/controller/auth/swagger.ts new file mode 100644 index 0000000..41ff77a --- /dev/null +++ b/src/auth/application/controller/auth/swagger.ts @@ -0,0 +1,143 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiBody, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; +import { + ApiBadRequest, + ApiConflict, + ApiForbidden, + ApiNotFound, + ApiUnauthorized, + ApiValidationError, +} from '@shared/error'; +import { SignInDto, SignUpDto, VerifyDto } from '../../dtos'; +import { ActionResponse } from '@shared/dtos'; + +export const PostRegisterSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Регистрация нового пользователя', + description: 'Создает пользователя, базовые настройки безопасности и уведомлений.', + }), + ApiBody({ type: SignUpDto.Output }), + ApiResponse({ + status: 201, + description: 'Пользователь успешно зарегистрирован.', + type: ActionResponse.Output, + }), + ApiValidationError('Ошибка валидации данных (например, неверный формат email)'), + ApiConflict('Пользователь с таким email уже существует'), + ); + +export const PostLoginSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Вход в систему', + description: + 'Возвращает Access/Refresh токены. Если у пользователя включена 2FA, вернет временный токен.', + }), + ApiBody({ type: SignInDto.Output }), + ApiResponse({ + status: 200, + description: 'Успешный вход.', + schema: { + example: { + success: true, + message: false, + token: 'eyJhbGciOiJIUzI1NiIsInR5c...', + }, + }, + }), + ApiBadRequest('Неверный формат email'), + ApiUnauthorized('Неверный email или пароль'), + ); + +export const PostRefreshSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Обновление токенов', + description: 'Выдает новую пару Access и Refresh токенов.', + }), + ApiResponse({ + status: 200, + description: 'Токены успешно обновлены.', + schema: { + example: { + success: true, + token: 'eyJhbGciOiJIUzI1NiIsInR5c...', + message: 'def50200508a1768c7e...', + }, + }, + }), + ApiBadRequest('Ошибка валидации (не передан refresh токен)'), + ApiUnauthorized('Refresh токен недействителен, истек или отозван'), + ); + +export const PostLogoutSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Выход из системы', + description: 'Удаляет текущую сессию пользователя из Redis.', + }), + ApiResponse({ status: 200, description: 'Успешный выход.', type: ActionResponse.Output }), + ApiUnauthorized(), + ); + +export const PostSignUpConfirmSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Подтверждение регистрации по коду', + description: + 'Проверяет OTP из письма, создаёт аккаунт, выдаёт access-токен в теле ответа и устанавливает refresh в httpOnly cookie.', + }), + ApiBody({ type: VerifyDto.Output }), + ApiResponse({ + status: 201, + description: 'Аккаунт подтверждён, сессия создана.', + schema: { + example: { + success: true, + message: 'Аккаунт успешно подтвержден', + token: 'eyJhbGciOiJIUzI1NiIsInR5c...', + }, + }, + }), + ApiValidationError('Ошибка валидации (неверный формат email или длина кода)'), + ApiBadRequest('Срок регистрации истёк или сессия не найдена'), + ApiBadRequest('Неверный или истёкший код подтверждения'), + ); + +export const GetSessionsSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Получить активные сессии', + description: 'Возвращает список всех активных устройств/сессий пользователя.', + }), + ApiResponse({ + status: 200, + description: 'Список сессий успешно получен.', + schema: { + example: [ + { + id: 'clj1xyz990000abc1', + device: 'Chrome on macOS', + ip: '192.168.1.1', + lastActive: '2026-04-11T14:30:00.000Z', + isCurrent: true, + }, + ], + }, + }), + ApiUnauthorized(), + ); + +export const DeleteSessionSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Завершить чужую сессию', + description: 'Принудительно удаляет указанную сессию из Redis.', + }), + ApiParam({ name: 'cuid', description: 'ID сессии, которую нужно завершить' }), + ApiResponse({ status: 200, description: 'Сессия успешно завершена.' }), + ApiUnauthorized(), + ApiForbidden(), + ApiNotFound('Сессия не найдена или уже истекла'), + ); diff --git a/src/auth/application/controller/index.ts b/src/auth/application/controller/index.ts new file mode 100644 index 0000000..c2c6838 --- /dev/null +++ b/src/auth/application/controller/index.ts @@ -0,0 +1,2 @@ +export { AuthController } from './auth/controller'; +export { AuthRecoveryController } from './recovery/controller'; diff --git a/src/auth/application/controller/recovery/controller.ts b/src/auth/application/controller/recovery/controller.ts new file mode 100644 index 0000000..e274427 --- /dev/null +++ b/src/auth/application/controller/recovery/controller.ts @@ -0,0 +1,32 @@ +import { ApiBaseController } from '@shared/decorators'; +import { Body, Post } from '@nestjs/common'; +import { + PostPasswordResetConfirmSwagger, + PostPasswordResetSwagger, + PostPasswordResetVerifySwagger, +} from './swagger'; +import { PasswordResetConfirmDto, ResetPasswordDto, VerifyResetCodeDto } from '../../dtos'; +import { AuthFacade } from '../../auth.facade'; + +@ApiBaseController('auth', 'Auth Recovery') +export class AuthRecoveryController { + constructor(private readonly facade: AuthFacade) {} + + @Post('password/reset') + @PostPasswordResetSwagger() + async sendResetCode(@Body() dto: ResetPasswordDto) { + return this.facade.sendResetCode(dto); + } + + @Post('password/reset/verify') + @PostPasswordResetVerifySwagger() + async verifyResetCode(@Body() dto: VerifyResetCodeDto) { + return this.facade.verifyResetCode(dto); + } + + @Post('password/reset/confirm') + @PostPasswordResetConfirmSwagger() + async confirmNewPassword(@Body() dto: PasswordResetConfirmDto) { + return this.facade.confirmNewPassword(dto); + } +} diff --git a/src/auth/application/controller/recovery/swagger.ts b/src/auth/application/controller/recovery/swagger.ts new file mode 100644 index 0000000..3e10cdc --- /dev/null +++ b/src/auth/application/controller/recovery/swagger.ts @@ -0,0 +1,173 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiBody, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; +import { + ApiBadRequest, + ApiErrorResponse, + ApiForbidden, + ApiNotFound, + ApiUnauthorized, + ApiValidationError, +} from '@shared/error'; +import { + ChangePasswordDto, + Confirm2FaDto, + Disable2FaDto, + PasswordResetConfirmDto, + ResetPasswordDto, + VerifyResetCodeDto, +} from '../../dtos'; +import { ActionResponse } from '@shared/dtos'; + +export const PostPasswordResetSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Запрос кода восстановления пароля', + description: 'Отправляет одноразовый код на email, если пользователь существует.', + }), + ApiBody({ type: ResetPasswordDto.Output }), + ApiResponse({ + status: 201, + description: 'Код отправлен на почту (при успешной обработке запроса).', + type: ActionResponse.Output, + }), + ApiValidationError('Некорректный формат email'), + ApiErrorResponse( + 422, + 'INVALID_EMAIL_FORMAT', + 'Указанный email адрес имеет некорректный формат', + ), + ApiNotFound('Пользователь с таким email не найден'), + ); + +export const PostPasswordResetVerifySwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Проверка кода восстановления пароля', + description: 'Проверяет код из письма и помечает сессию сброса как подтверждённую.', + }), + ApiBody({ type: VerifyResetCodeDto.Output }), + ApiResponse({ + status: 201, + description: 'Код подтверждён, можно задать новый пароль.', + type: ActionResponse.Output, + }), + ApiValidationError('Ошибка валидации (email или формат кода)'), + ApiBadRequest('Время подтверждения истекло или запрос не найден'), + ApiBadRequest('Неверный или истёкший код подтверждения'), + ); + +export const PostPasswordResetConfirmSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Установка нового пароля после сброса', + description: 'Доступно только после успешной проверки кода на шаге verify.', + }), + ApiBody({ type: PasswordResetConfirmDto.Output }), + ApiResponse({ + status: 201, + description: 'Пароль успешно изменён.', + type: ActionResponse.Output, + }), + ApiValidationError('Ошибка валидации (пароли не совпадают или неверная длина)'), + ApiBadRequest('Сессия восстановления не найдена или истекла'), + ApiForbidden(), + ApiErrorResponse( + 500, + 'PASSWORD_UPDATE_FAILED', + 'Не удалось обновить пароль. Попробуйте позже.', + ), + ); + +export const GetSessionsSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Получить активные сессии', + description: 'Возвращает список всех активных устройств/сессий пользователя.', + }), + ApiResponse({ + status: 200, + description: 'Список сессий успешно получен.', + schema: { + example: [ + { + id: 'clj1xyz990000abc1', + device: 'Chrome on macOS', + ip: '192.168.1.1', + lastActive: '2026-04-11T14:30:00.000Z', + isCurrent: true, + }, + ], + }, + }), + ApiUnauthorized(), + ); + +export const DeleteSessionSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Завершить чужую сессию', + description: 'Принудительно удаляет указанную сессию из Redis.', + }), + ApiParam({ name: 'cuid', description: 'ID сессии, которую нужно завершить' }), + ApiResponse({ status: 200, description: 'Сессия успешно завершена.' }), + ApiUnauthorized(), + ApiForbidden(), + ApiNotFound('Сессия не найдена или уже истекла'), + ); + +export const PostChangePasswordSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Смена пароля', + description: 'Требует текущий и новый пароль. Инвалидирует все остальные сессии.', + }), + ApiBody({ type: ChangePasswordDto.Output }), + ApiResponse({ status: 200, description: 'Пароль успешно изменен.' }), + ApiBadRequest('Неверный старый пароль'), + ApiUnauthorized(), + ); + +export const PostEnable2faSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Генерация QR-кода для 2FA', + description: 'Создает секрет и возвращает ссылку (otpauth) для Google Authenticator.', + }), + ApiResponse({ + status: 200, + description: 'QR-код сгенерирован.', + schema: { + example: { + secret: 'JBSWY3DPEHPK3PXP', + qrCodeUrl: + 'otpauth://totp/TaskTracker:alexey?secret=JBSWY3DPEHPK3PXP&issuer=TaskTracker', + }, + }, + }), + ApiUnauthorized(), + ); + +export const PostDisable2faSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Подтверждение включения 2FA', + description: 'Проверяет первый код из приложения для окончательной активации 2FA.', + }), + ApiBody({ type: Confirm2FaDto.Output }), + ApiResponse({ status: 200, description: 'Двухфакторная аутентификация успешно включена.' }), + ApiBadRequest('Неверный код подтверждения'), + ApiUnauthorized(), + ); + +export const PostConfirm2faSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Отключение 2FA', + description: + 'Отключает двухфакторную аутентификацию (требует подтверждения паролем или текущим кодом).', + }), + ApiBody({ type: Disable2FaDto.Output }), + ApiResponse({ status: 200, description: '2FA успешно отключена.' }), + ApiBadRequest('Неверный код или пароль для отключения'), + ApiUnauthorized(), + ); diff --git a/src/auth/application/dtos/2fa.dto.ts b/src/auth/application/dtos/2fa.dto.ts new file mode 100644 index 0000000..8d10068 --- /dev/null +++ b/src/auth/application/dtos/2fa.dto.ts @@ -0,0 +1,25 @@ +import z from 'zod/v4'; +import { createZodDto } from 'nestjs-zod'; + +export const Confirm2FaSchema = z + .object({ + code: z + .string() + .length(6, 'Код должен состоять ровно из 6 символов') + .describe('6-значный код из Google Authenticator'), + }) + .describe('Схема подтверждения 2FA'); + +export class Confirm2FaDto extends createZodDto(Confirm2FaSchema) {} + +export const Disable2FaSchema = z + .object({ + password: z.string().optional().describe('Текущий пароль для подтверждения (опционально)'), + code: z.string().optional().describe('Код из приложения (опционально)'), + }) + .refine((data) => data.password || data.code, { + message: 'Нужно передать либо пароль, либо код', + }) + .describe('Схема отключения 2FA'); + +export class Disable2FaDto extends createZodDto(Disable2FaSchema) {} diff --git a/src/auth/application/dtos/auth.dto.ts b/src/auth/application/dtos/auth.dto.ts new file mode 100644 index 0000000..5aeac05 --- /dev/null +++ b/src/auth/application/dtos/auth.dto.ts @@ -0,0 +1,58 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod/v4'; + +export const SignInSchema = z + .object({ + email: z.email('Некорректный формат email').describe('Email пользователя'), + password: z.string().describe('Пароль пользователя'), + }) + .describe('Схема входа в систему'); + +export class SignInDto extends createZodDto(SignInSchema) {} + +export const SignUpSchema = z + .object({ + email: z.email('Некорректный формат email').describe('Email пользователя'), + password: z + .string() + .min(8, 'Пароль должен содержать минимум 8 символов') + .max(32, 'Пароль должен содержать максимум 32 символа') + .describe('Пароль (минимум 8 символов)'), + firstName: z + .string() + .min(2, 'Имя должно содержать минимум 2 символа') + .max(50) + .trim() + .describe('Имя'), + lastName: z + .string() + .min(2, 'Фамилия должна содержать минимум 2 символа') + .max(50) + .trim() + .describe('Фамилия'), + middleName: z + .string() + .max(50) + .trim() + .optional() + .or(z.literal('')) + .describe('Отчество (опционально)'), + }) + .describe('Схема регистрации пользователя'); + +export class SignUpDto extends createZodDto(SignUpSchema) {} + +export const VerifySchema = z + .object({ + email: z + .string() + .email('Некорректный формат email') + .describe('Email пользователя, на который был отправлен код'), + code: z + .string() + .length(6, 'Код должен содержать ровно 6 символов') + .describe('6-значный OTP код подтверждения'), + }) + .describe('Схема верификации OTP кода'); + +export class VerifyDto extends createZodDto(VerifySchema) {} diff --git a/src/auth/application/dtos/index.ts b/src/auth/application/dtos/index.ts new file mode 100644 index 0000000..6a0829f --- /dev/null +++ b/src/auth/application/dtos/index.ts @@ -0,0 +1,3 @@ +export * from './auth.dto'; +export * from './2fa.dto'; +export * from './password.dto'; diff --git a/src/auth/application/dtos/password.dto.ts b/src/auth/application/dtos/password.dto.ts new file mode 100644 index 0000000..e0a260f --- /dev/null +++ b/src/auth/application/dtos/password.dto.ts @@ -0,0 +1,45 @@ +import { createZodDto } from 'nestjs-zod'; +import z from 'zod/v4'; + +export const ChangePasswordSchema = z + .object({ + oldPassword: z.string().describe('Текущий пароль'), + newPassword: z + .string() + .min(8, 'Новый пароль должен содержать минимум 8 символов') + .max(32, 'Новый пароль должен содержать максимум 32 символа') + .describe('Новый пароль (минимум 8 символов)'), + }) + .describe('Схема смены пароля'); + +export class ChangePasswordDto extends createZodDto(ChangePasswordSchema) {} + +export const ResetPasswordSchema = z.object({ + email: z.string().email('Некорректный формат email').describe('Email для восстановления'), +}); + +export class ResetPasswordDto extends createZodDto(ResetPasswordSchema) {} + +export const VerifyResetCodeSchema = z.object({ + email: z.string().email(), + code: z.string().length(6, 'Код должен содержать 6 цифр').describe('Код из письма'), +}); + +export class VerifyResetCodeDto extends createZodDto(VerifyResetCodeSchema) {} + +export const PasswordResetConfirmSchema = z + .object({ + email: z.string().email(), + password: z + .string() + .min(8, 'Минимум 8 символов') + .max(32, 'Максимум 32 символа') + .describe('Новый пароль'), + confirmPassword: z.string().describe('Повторите новый пароль'), + }) + .refine((data) => data.password === data.confirmPassword, { + message: 'Пароли не совпадают', + path: ['confirmPassword'], + }); + +export class PasswordResetConfirmDto extends createZodDto(PasswordResetConfirmSchema) {} diff --git a/src/auth/application/use-cases/confirm-reset-password.use-case.ts b/src/auth/application/use-cases/confirm-reset-password.use-case.ts new file mode 100644 index 0000000..cdbf3b6 --- /dev/null +++ b/src/auth/application/use-cases/confirm-reset-password.use-case.ts @@ -0,0 +1,64 @@ +import { InjectRedis } from '@nestjs-modules/ioredis'; +import { HttpStatus, Injectable } from '@nestjs/common'; +import * as argon from 'argon2'; +import Redis from 'ioredis'; +import { BaseException } from '@shared/error'; +import { PasswordResetConfirmDto } from '../dtos'; +import { UpdatePasswordUseCase } from '@core/user'; + +@Injectable() +export class ConfirmResetPasswordUseCase { + constructor( + @InjectRedis() + private readonly redis: Redis, + private readonly updatePasswordUserUseCase: UpdatePasswordUseCase, + ) {} + + async execute(dto: PasswordResetConfirmDto) { + const redisKey = `pass:reset:${dto.email}`; + const cachedData = await this.redis.get(redisKey); + + if (!cachedData) { + throw new BaseException( + { + code: 'RESET_SESSION_NOT_FOUND', + message: + 'Сессия восстановления не найдена или истекла. Начните процесс заново.', + }, + HttpStatus.BAD_REQUEST, + ); + } + + const resetSession = JSON.parse(cachedData); + + if (!resetSession.isVerified) { + throw new BaseException( + { + code: 'CODE_NOT_VERIFIED', + message: 'Код подтверждения еще не был верифицирован.', + details: [{ target: 'isVerified', value: false }], + }, + HttpStatus.FORBIDDEN, + ); + } + + const hashed = await argon.hash(dto.password); + const isUpdated = await this.updatePasswordUserUseCase.execute(dto.email, hashed); + + if (!isUpdated) { + throw new BaseException( + { + code: 'PASSWORD_UPDATE_FAILED', + message: 'Не удалось обновить пароль. Попробуйте позже.', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + await this.redis.del(redisKey); + + return { + success: true, + message: 'Пароль успешно изменен. Теперь вы можете войти в аккаунт.', + }; + } +} diff --git a/src/auth/application/use-cases/index.ts b/src/auth/application/use-cases/index.ts new file mode 100644 index 0000000..97a9482 --- /dev/null +++ b/src/auth/application/use-cases/index.ts @@ -0,0 +1,8 @@ +export { ConfirmResetPasswordUseCase } from './confirm-reset-password.use-case'; +export { VerifyResetPasswordUseCase } from './verify-reset-password.use-case'; +export { RefreshTokensUseCase } from './refresh-tokens.use-case'; +export { ResetPasswordUseCase } from './reset-password.use-case'; +export { SignUpVerifyUseCase } from './sign-up-verify.use-case'; +export { SignInUseCase } from './sign-in.use-case'; +export { SignOutUseCase } from './sign-out.use-case'; +export { SignUpUseCase } from './sign-up.use-case'; diff --git a/src/auth/application/use-cases/refresh-tokens.use-case.ts b/src/auth/application/use-cases/refresh-tokens.use-case.ts new file mode 100644 index 0000000..390c752 --- /dev/null +++ b/src/auth/application/use-cases/refresh-tokens.use-case.ts @@ -0,0 +1,71 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { BaseException } from '@shared/error'; +import { ISessionRepository } from '../../domain/repository'; +import { TokenService } from '../../infrastructure/security'; +import { DeviceMetadata } from '../../infrastructure/utils/get-device-meta'; +import { FindUserQuery } from '@core/user'; + +@Injectable() +export class RefreshTokensUseCase { + constructor( + @Inject('ISessionRepository') + private readonly sessionRepo: ISessionRepository, + private readonly tokenService: TokenService, + private readonly findUserQuery: FindUserQuery, + ) {} + + async execute(token: string, metadata: DeviceMetadata) { + const payload = await this.tokenService.validateToken(token, 'refresh'); + + if (!payload?.jti) { + throw new BaseException( + { + code: 'INVALID_TOKEN', + message: 'Сессия недействительна или истекла', + }, + HttpStatus.UNAUTHORIZED, + ); + } + + const session = await this.sessionRepo.findById(payload.jti); + + if (!session || session.isRevoked) { + throw new BaseException( + { + code: 'SESSION_REVOKED', + message: 'Ваша сессия была отозвана или завершена', + }, + HttpStatus.UNAUTHORIZED, + ); + } + + const { user } = await this.findUserQuery.execute({ id: session.userId }); + + if (!user) { + await this.sessionRepo.revoke(session.id); + throw new BaseException( + { + code: 'USER_NOT_FOUND', + message: 'Аккаунт пользователя не найден', + }, + HttpStatus.UNAUTHORIZED, + ); + } + + await this.sessionRepo.revoke(session.id); + + const newSession = await this.sessionRepo.create({ + userId: user.id, + ...metadata, + expiresAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), + }); + + const { access, refresh } = await this.tokenService.generateTokens(user, newSession.id); + + return { + tokens: { access, refresh }, + success: true, + message: 'Токены успешно обновлены', + }; + } +} diff --git a/src/auth/application/use-cases/reset-password.use-case.ts b/src/auth/application/use-cases/reset-password.use-case.ts new file mode 100644 index 0000000..f930b56 --- /dev/null +++ b/src/auth/application/use-cases/reset-password.use-case.ts @@ -0,0 +1,67 @@ +import { InjectRedis } from '@nestjs-modules/ioredis'; +import { InjectQueue } from '@nestjs/bullmq'; +import { HttpStatus, Injectable } from '@nestjs/common'; +import { Queue } from 'bullmq'; +import Redis from 'ioredis'; +import { generate, generateSecret } from 'otplib'; +import { BaseException } from '@shared/error'; +import { AuthMailJobs, AuthQueues } from '../../domain/enums'; +import { ResetPasswordEvent } from '../../domain/events'; +import { ResetPasswordDto } from '../dtos'; +import { FindUserQuery } from '@core/user'; + +@Injectable() +export class ResetPasswordUseCase { + constructor( + @InjectRedis() + private readonly redis: Redis, + @InjectQueue(AuthQueues.AUTH_MAIL) + private readonly mailQueue: Queue, + private readonly findUserQuery: FindUserQuery, + ) {} + + async execute(dto: ResetPasswordDto) { + const entity = await this.findUserQuery.execute({ email: dto.email }); + + if (!entity.user) { + throw new BaseException( + { + code: 'USER_NOT_FOUND', + message: 'Пользователь с таким email не найден', + details: [{ target: 'email', value: dto.email }], + }, + HttpStatus.NOT_FOUND, + ); + } + + const secret = generateSecret(); + const token = await generate({ + secret, + digits: 6, + period: 900, + strategy: 'totp', + }); + + const resetPayload = { + email: entity.user.email, + otp: { secret, token }, + isVerified: false, + }; + + await this.redis.set(`pass:reset:${dto.email}`, JSON.stringify(resetPayload), 'EX', 900); + + const event = new ResetPasswordEvent(dto.email, token); + await this.mailQueue.add(AuthMailJobs.SEND_RESET_PASSWORD, event, { + attempts: 3, + backoff: { + type: 'exponential', + delay: 5000, + }, + }); + + return { + success: true, + message: 'Код для восстановления пароля отправлен на вашу почту', + }; + } +} diff --git a/src/auth/application/use-cases/sign-in.use-case.ts b/src/auth/application/use-cases/sign-in.use-case.ts new file mode 100644 index 0000000..05c4d04 --- /dev/null +++ b/src/auth/application/use-cases/sign-in.use-case.ts @@ -0,0 +1,62 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import * as argon from 'argon2'; +import { BaseException } from '@shared/error'; +import { ISessionRepository } from '../../domain/repository'; +import { TokenService } from '../../infrastructure/security'; +import { DeviceMetadata } from '../../infrastructure/utils/get-device-meta'; +import { SignInDto } from '../dtos'; +import { FindUserQuery } from '@core/user'; + +@Injectable() +export class SignInUseCase { + constructor( + @Inject('ISessionRepository') + private readonly sessionRepo: ISessionRepository, + private readonly tokenService: TokenService, + private readonly findUserQuery: FindUserQuery, + ) {} + + async execute(dto: SignInDto, meta: DeviceMetadata) { + const entities = await this.findUserQuery.execute({ email: dto.email }); + + if (!entities?.user || !entities?.security) { + throw new BaseException( + { + code: 'INVALID_CREDENTIALS', + message: 'Неверный email или пароль', + }, + HttpStatus.UNAUTHORIZED, + ); + } + + const { security, user } = entities; + const isPasswordValid = await argon.verify(security.passwordHash, dto.password); + + if (!isPasswordValid) { + throw new BaseException( + { + code: 'INVALID_CREDENTIALS', + message: 'Неверный email или пароль', + }, + HttpStatus.UNAUTHORIZED, + ); + } + + const { id } = await this.sessionRepo.create({ + userId: user.id, + expiresAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), + ...meta, + }); + + const { access, refresh } = await this.tokenService.generateTokens(user, id); + + return { + success: true, + tokens: { + access, + refresh, + }, + message: 'Вы успешно вошли в систему', + }; + } +} diff --git a/src/auth/application/use-cases/sign-out.use-case.ts b/src/auth/application/use-cases/sign-out.use-case.ts new file mode 100644 index 0000000..23d85fc --- /dev/null +++ b/src/auth/application/use-cases/sign-out.use-case.ts @@ -0,0 +1,46 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { BaseException } from '@shared/error'; +import { ISessionRepository } from '../../domain/repository'; +import { TokenService } from '../../infrastructure/security'; + +@Injectable() +export class SignOutUseCase { + constructor( + @Inject('ISessionRepository') + private readonly sessionRepo: ISessionRepository, + private readonly tokenService: TokenService, + ) {} + + async execute(token: string) { + const payload = await this.tokenService.validateToken(token, 'refresh'); + + if (!payload?.jti) { + throw new BaseException( + { + code: 'SESSION_EXPIRED', + message: 'Сессия уже истекла', + }, + HttpStatus.UNAUTHORIZED, + ); + } + + const session = await this.sessionRepo.findById(payload.jti); + + if (session) { + const isRevoked = await this.sessionRepo.revoke(session.id); + + if (!isRevoked) { + throw new BaseException( + { + code: 'SIGNOUT_FAILED', + message: 'Не удалось завершить сессию на сервере. Попробуйте позже.', + details: [{ target: 'database', message: 'Session revocation failed' }], + }, + HttpStatus.SERVICE_UNAVAILABLE, + ); + } + } + + return { success: true, message: 'Успешно вышли из системы!' }; + } +} diff --git a/src/auth/application/use-cases/sign-up-verify.use-case.ts b/src/auth/application/use-cases/sign-up-verify.use-case.ts new file mode 100644 index 0000000..6046c73 --- /dev/null +++ b/src/auth/application/use-cases/sign-up-verify.use-case.ts @@ -0,0 +1,81 @@ +import { InjectRedis } from '@nestjs-modules/ioredis'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import Redis from 'ioredis'; +import { verify as verifyOTP } from 'otplib'; +import { RegisterUserUseCase } from '@core/user'; +import { BaseException } from '@shared/error'; +import { ISessionRepository } from '../../domain/repository'; +import { TokenService } from '../../infrastructure/security'; +import { DeviceMetadata } from '../../infrastructure/utils/get-device-meta'; +import { VerifyDto } from '../dtos'; + +@Injectable() +export class SignUpVerifyUseCase { + constructor( + @InjectRedis() + private readonly redis: Redis, + @Inject('ISessionRepository') + private readonly sessionRepo: ISessionRepository, + private readonly tokenService: TokenService, + private readonly registerUserUseCase: RegisterUserUseCase, + ) {} + + async execute(dto: VerifyDto, meta: DeviceMetadata) { + const redisKey = `reg:${dto.email}`; + + const cachedData = await this.redis.get(redisKey); + + if (!cachedData) { + throw new BaseException( + { + code: 'REGISTRATION_EXPIRED', + message: 'Срок регистрации истек или email не найден. Попробуйте снова.', + }, + HttpStatus.GONE, + ); + } + + const userData = JSON.parse(cachedData); + + const verifyResult = await verifyOTP({ + token: dto.code, + secret: userData.otp.secret, + algorithm: 'sha256', + digits: 6, + period: 900, + strategy: 'totp', + afterTimeStep: 1, + }); + + if (!verifyResult.valid) { + throw new BaseException( + { + code: 'INVALID_OTP', + message: 'Неверный или истекший код подтверждения', + details: [{ target: 'code', message: 'OTP code is invalid or expired' }], + }, + HttpStatus.BAD_REQUEST, + ); + } + + const user = await this.registerUserUseCase.execute({ + ...userData.user, + password: userData.password, + }); + + const session = await this.sessionRepo.create({ + userId: user.id, + expiresAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), + ...meta, + }); + const { access, refresh } = await this.tokenService.generateTokens(user, session.id); + + await this.redis.del(redisKey); + + return { + success: true, + tokens: { access, refresh }, + message: 'Аккаунт успешно подтвержден', + }; + } +} diff --git a/src/auth/application/use-cases/sign-up.use-case.ts b/src/auth/application/use-cases/sign-up.use-case.ts new file mode 100644 index 0000000..5550c06 --- /dev/null +++ b/src/auth/application/use-cases/sign-up.use-case.ts @@ -0,0 +1,86 @@ +import { InjectRedis } from '@nestjs-modules/ioredis'; +import { InjectQueue } from '@nestjs/bullmq'; +import { HttpStatus, Injectable } from '@nestjs/common'; +import * as argon from 'argon2'; +import { Queue } from 'bullmq'; +import Redis from 'ioredis'; +import { generate, generateSecret } from 'otplib'; +import { FindUserQuery } from '@core/user'; +import { BaseException } from '@shared/error'; +import { AuthQueues, AuthMailJobs } from '../../domain/enums'; +import { RegisterCodeEvent } from '../../domain/events'; +import { SignUpDto } from '../dtos'; + +@Injectable() +export class SignUpUseCase { + constructor( + @InjectRedis() + private readonly redis: Redis, + @InjectQueue(AuthQueues.AUTH_MAIL) + private readonly mailQueue: Queue, + private readonly findUserQuery: FindUserQuery, + ) {} + + async execute(dto: SignUpDto) { + const redisKey = `reg:${dto.email}`; + + const cachedData = await this.redis.get(redisKey); + + if (cachedData) { + throw new BaseException( + { + code: 'REGISTRATION_IN_PROGRESS', + message: 'Код уже был отправлен. Проверьте почту или подождите 15 минут.', + details: [{ target: 'email', message: 'Verification code already sent' }], + }, + HttpStatus.BAD_REQUEST, + ); + } + + const isExists = await this.findUserQuery.execute({ email: dto.email }); + + if (isExists) { + throw new BaseException( + { + code: 'USER_ALREADY_EXISTS', + message: 'Email уже занят другим аккаунтом', + details: [{ target: 'email', value: dto.email }], + }, + HttpStatus.CONFLICT, + ); + } + + const hashPass = await argon.hash(dto.password); + + const secret = generateSecret(); + const token = await generate({ + secret, + algorithm: 'sha256', + digits: 6, + period: 900, + strategy: 'totp', + }); + + const data = { + user: dto, + password: hashPass, + otp: { token, secret }, + }; + + await this.redis.set(`reg:${dto.email}`, JSON.stringify(data), 'EX', 900); + + const event = new RegisterCodeEvent(dto.email, dto.firstName, token); + await this.mailQueue.add(AuthMailJobs.SEND_REGISTER_CODE, event, { + attempts: 3, + backoff: { + type: 'exponential', + delay: 5000, + }, + }); + + return { + success: true, + message: 'Код подтверждения отправлен на вашу почту', + }; + } +} diff --git a/src/auth/application/use-cases/verify-reset-password.use-case.ts b/src/auth/application/use-cases/verify-reset-password.use-case.ts new file mode 100644 index 0000000..842ae33 --- /dev/null +++ b/src/auth/application/use-cases/verify-reset-password.use-case.ts @@ -0,0 +1,63 @@ +import { InjectRedis } from '@nestjs-modules/ioredis'; +import { HttpStatus, Injectable } from '@nestjs/common'; +import Redis from 'ioredis'; +import { verify as verifyOTP } from 'otplib'; +import { BaseException } from '@shared/error'; +import { VerifyResetCodeDto } from '../dtos'; + +@Injectable() +export class VerifyResetPasswordUseCase { + constructor( + @InjectRedis() + private readonly redis: Redis, + ) {} + + async execute(dto: VerifyResetCodeDto) { + const redisKey = `pass:reset:${dto.email}`; + const cachedData = await this.redis.get(redisKey); + + if (!cachedData) { + throw new BaseException( + { + code: 'RESET_SESSION_EXPIRED', + message: + 'Время подтверждения истекло или запрос не найден. Запросите код снова.', + }, + HttpStatus.GONE, + ); + } + + const resetSession = JSON.parse(cachedData); + + const verifyResult = await verifyOTP({ + token: dto.code, + secret: resetSession.otp.secret, + digits: 6, + period: 900, + strategy: 'totp', + }); + + if (!verifyResult.valid) { + throw new BaseException( + { + code: 'INVALID_VERIFICATION_CODE', + message: 'Неверный или истекший код подтверждения', + details: [{ target: 'code', message: 'The provided OTP is incorrect' }], + }, + HttpStatus.BAD_REQUEST, + ); + } + + await this.redis.set( + redisKey, + JSON.stringify({ ...resetSession, isVerified: true }), + 'EX', + 600, + ); + + return { + success: true, + message: 'Код успешно подтвержден. Теперь вы можете установить новый пароль.', + }; + } +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..e79f42a --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,115 @@ +import { BullBoardModule } from '@bull-board/nestjs'; +import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; +import { RedisModule } from '@nestjs-modules/ioredis'; +import { BullModule } from '@nestjs/bullmq'; +import { Module, forwardRef } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { JwtModule } from '@nestjs/jwt'; +import { UserModule } from '@core/user'; +import { AuthController, AuthRecoveryController } from './application/controller'; +import { AuthFacade } from './application/auth.facade'; +import { + ConfirmResetPasswordUseCase, + VerifyResetPasswordUseCase, + RefreshTokensUseCase, + ResetPasswordUseCase, + SignUpVerifyUseCase, + SignInUseCase, + SignOutUseCase, + SignUpUseCase, +} from './application/use-cases'; +import { AuthQueues } from './domain/enums'; +import { SessionRepository } from './infrastructure/persistence/repositories'; +import { TokenService } from './infrastructure/security'; +import { BearerStrategy, CookieStrategy } from './infrastructure/strategies'; +import { MailProcessor } from './infrastructure/workers'; +import { MailAdapter } from '@shared/adapters/mail'; + +const USE_CASES = [ + ConfirmResetPasswordUseCase, + VerifyResetPasswordUseCase, + RefreshTokensUseCase, + ResetPasswordUseCase, + SignUpVerifyUseCase, + SignInUseCase, + SignOutUseCase, + SignUpUseCase, +]; + +const WORKERS = [MailProcessor]; + +const REPOSITORY = { + provide: 'ISessionRepository', + useClass: SessionRepository, +}; + +@Module({ + imports: [ + JwtModule.registerAsync({ + inject: [ConfigService], + useFactory: async (cfg: ConfigService) => ({ + secret: cfg.get('JWT_ACCESS_SECRET'), + signOptions: { + /** + * Использование 'any' здесь необходимо, так как Zod гарантирует + * формат строки (напр. '15m', '30d') через regex в ConfigSchema, но внутренний тип + * 'StringValue' из библиотеки 'ms' слишком строг для обычного string. + */ + expiresIn: cfg.get('JWT_ACCESS_EXPIRES_IN'), + algorithm: 'HS256', + }, + verifyOptions: { + algorithms: ['HS256'], + ignoreExpiration: false, + clockTolerance: 10, + }, + }), + }), + RedisModule.forRootAsync({ + inject: [ConfigService], + useFactory: async (cfg: ConfigService) => { + const host = cfg.getOrThrow('REDIS_HOST', { infer: true }); + const port = cfg.get('REDIS_PORT'); + const password = cfg.get('REDIS_PASSWORD'); + const url = `redis://${host}${port ? `:${port}` : ''}`; + + return { + type: 'single', + url, + options: { + password, + retryStrategy(times) { + return Math.min(times * 50, 2000); + }, + commandTimeout: 3000, + }, + }; + }, + }), + BullModule.registerQueue({ + name: AuthQueues.AUTH_MAIL, + }), + BullBoardModule.forFeature({ + name: AuthQueues.AUTH_MAIL, + adapter: BullMQAdapter, + }), + forwardRef(() => UserModule), + ], + controllers: [AuthController, AuthRecoveryController], + providers: [ + // TOOD: FIX PROVIDER + { + provide: 'IMailPort', + useClass: MailAdapter, + }, + ...WORKERS, + TokenService, + CookieStrategy, + BearerStrategy, + REPOSITORY, + ...USE_CASES, + AuthFacade, + ], + exports: [], +}) +export class AuthModule {} diff --git a/src/auth/domain/domain/.gitkeep b/src/auth/domain/domain/.gitkeep new file mode 100644 index 0000000..42e6429 --- /dev/null +++ b/src/auth/domain/domain/.gitkeep @@ -0,0 +1 @@ +# feature added entity class \ No newline at end of file diff --git a/src/auth/domain/enums/index.ts b/src/auth/domain/enums/index.ts new file mode 100644 index 0000000..a2f814f --- /dev/null +++ b/src/auth/domain/enums/index.ts @@ -0,0 +1 @@ +export { AuthMailJobs, AuthQueues } from './mail-jobs.enum'; diff --git a/src/auth/domain/enums/mail-jobs.enum.ts b/src/auth/domain/enums/mail-jobs.enum.ts new file mode 100644 index 0000000..e9ff5ab --- /dev/null +++ b/src/auth/domain/enums/mail-jobs.enum.ts @@ -0,0 +1,9 @@ +export enum AuthQueues { + AUTH_MAIL = 'AUTH_MAIL_QUEUE', +} + +export enum AuthMailJobs { + SEND_REGISTER_CODE = 'AUTH_SEND_REGISTER_CODE', + SEND_RESET_PASSWORD = 'AUTH_SEND_RESET_PASSWORD', + SEND_CHANGE_EMAIL = 'AUTH_SEND_CHANGE_EMAIL', +} diff --git a/src/auth/domain/events/index.ts b/src/auth/domain/events/index.ts new file mode 100644 index 0000000..61a6360 --- /dev/null +++ b/src/auth/domain/events/index.ts @@ -0,0 +1,2 @@ +export { RegisterCodeEvent } from './register-code.event'; +export { ResetPasswordEvent } from './reset-password.event'; diff --git a/src/auth/domain/events/register-code.event.ts b/src/auth/domain/events/register-code.event.ts new file mode 100644 index 0000000..df87ca8 --- /dev/null +++ b/src/auth/domain/events/register-code.event.ts @@ -0,0 +1,7 @@ +export class RegisterCodeEvent { + constructor( + public email: string, + public name: string, + public otp: string, + ) {} +} diff --git a/src/auth/domain/events/reset-password.event.ts b/src/auth/domain/events/reset-password.event.ts new file mode 100644 index 0000000..1f50e09 --- /dev/null +++ b/src/auth/domain/events/reset-password.event.ts @@ -0,0 +1,6 @@ +export class ResetPasswordEvent { + constructor( + public email: string, + public otp: string, + ) {} +} diff --git a/src/auth/domain/repository/index.ts b/src/auth/domain/repository/index.ts new file mode 100644 index 0000000..298c188 --- /dev/null +++ b/src/auth/domain/repository/index.ts @@ -0,0 +1 @@ +export * from './session.repository.interface'; diff --git a/src/auth/domain/repository/session.repository.interface.ts b/src/auth/domain/repository/session.repository.interface.ts new file mode 100644 index 0000000..e83a682 --- /dev/null +++ b/src/auth/domain/repository/session.repository.interface.ts @@ -0,0 +1,13 @@ +import { sessions } from '../../infrastructure/persistence/models/session.model'; + +export type SessionInsert = typeof sessions.$inferInsert; +export type SessionSelect = typeof sessions.$inferSelect; + +export interface ISessionRepository { + create(data: SessionInsert): Promise; + findById(id: string): Promise; + findAllByUserId(userId: string): Promise; + revoke(id: string): Promise; + revokeAllByUserId(userId: string, exceptSessionId?: string): Promise; + deleteExpired(): Promise; +} diff --git a/src/auth/infrastructure/persistence/models/index.ts b/src/auth/infrastructure/persistence/models/index.ts new file mode 100644 index 0000000..9b52ede --- /dev/null +++ b/src/auth/infrastructure/persistence/models/index.ts @@ -0,0 +1 @@ +export { sessions } from './session.model'; diff --git a/src/auth/infrastructure/persistence/models/session.model.ts b/src/auth/infrastructure/persistence/models/session.model.ts new file mode 100644 index 0000000..db788ae --- /dev/null +++ b/src/auth/infrastructure/persistence/models/session.model.ts @@ -0,0 +1,24 @@ +import { createId } from '@paralleldrive/cuid2'; +import { text, timestamp, varchar } from 'drizzle-orm/pg-core'; +import { boolean } from 'drizzle-orm/pg-core'; +import { baseSchema, users } from '@shared/entities'; + +export const sessions = baseSchema.table('sessions', { + id: text('id') + .primaryKey() + .$defaultFn(() => createId()), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), + deviceType: varchar('device_type', { length: 20 }).$type<'mobile' | 'desktop' | 'tablet'>(), + browser: varchar('browser', { length: 50 }), + os: varchar('os', { length: 50 }), + userAgent: text('user_agent').notNull(), + ip: varchar('ip', { length: 45 }).notNull(), + city: varchar('city', { length: 100 }), + countryCode: varchar('country_code', { length: 5 }), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), + expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), + isRevoked: boolean('is_revoked').default(false).notNull(), +}); diff --git a/src/auth/infrastructure/persistence/repositories/index.ts b/src/auth/infrastructure/persistence/repositories/index.ts new file mode 100644 index 0000000..d223cdb --- /dev/null +++ b/src/auth/infrastructure/persistence/repositories/index.ts @@ -0,0 +1 @@ +export { SessionRepository } from './session.repository'; diff --git a/src/auth/infrastructure/persistence/repositories/session.repository.ts b/src/auth/infrastructure/persistence/repositories/session.repository.ts new file mode 100644 index 0000000..709593a --- /dev/null +++ b/src/auth/infrastructure/persistence/repositories/session.repository.ts @@ -0,0 +1,66 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { eq, and, ne, lt, desc } from 'drizzle-orm'; +import * as schema from '../models'; +import { DATABASE_SERVICE, DatabaseService } from '@libs/database'; +import { ISessionRepository, type SessionInsert } from '../../../domain/repository'; + +@Injectable() +export class SessionRepository implements ISessionRepository { + constructor( + @Inject(DATABASE_SERVICE) + private readonly db: DatabaseService, + ) {} + + async create(data: SessionInsert) { + const [result] = await this.db.insert(schema.sessions).values(data).returning(); + return result; + } + + async findById(id: string) { + const [result] = await this.db + .select() + .from(schema.sessions) + .where(and(eq(schema.sessions.id, id), eq(schema.sessions.isRevoked, false))) + .limit(1); + + return result || null; + } + + async findAllByUserId(userId: string) { + return this.db + .select() + .from(schema.sessions) + .where(and(eq(schema.sessions.userId, userId), eq(schema.sessions.isRevoked, false))) + .orderBy(desc(schema.sessions.createdAt)); + } + + async revoke(id: string) { + const { rowCount } = await this.db + .update(schema.sessions) + .set({ isRevoked: true, updatedAt: new Date() }) + .where(eq(schema.sessions.id, id)); + + return (rowCount ?? 0) > 0; + } + + async revokeAllByUserId(userId: string, exceptSessionId?: string) { + const filters = [eq(schema.sessions.userId, userId)]; + + if (exceptSessionId) { + filters.push(ne(schema.sessions.id, exceptSessionId)); + } + + await this.db + .update(schema.sessions) + .set({ isRevoked: true, updatedAt: new Date() }) + .where(and(...filters)); + } + + async deleteExpired() { + const result = await this.db + .delete(schema.sessions) + .where(lt(schema.sessions.expiresAt, new Date())); + + return result.rowCount; + } +} diff --git a/src/auth/infrastructure/security/index.ts b/src/auth/infrastructure/security/index.ts new file mode 100644 index 0000000..0b27e01 --- /dev/null +++ b/src/auth/infrastructure/security/index.ts @@ -0,0 +1 @@ +export { TokenService } from './token.service'; diff --git a/src/auth/infrastructure/security/token.service.ts b/src/auth/infrastructure/security/token.service.ts new file mode 100644 index 0000000..a3f2480 --- /dev/null +++ b/src/auth/infrastructure/security/token.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { ConfigService } from '@nestjs/config'; +import type { JwtPayload } from '@shared/types'; +import type { User } from '@core/user'; + +@Injectable() +export class TokenService { + constructor( + private readonly jwtService: JwtService, + private readonly cfg: ConfigService, + ) {} + + async generateTokens(user: User, sessionId: string) { + const domain = this.cfg.get('DOMAIN'); + const audConstraint = this.cfg.getOrThrow('JWT_AUDIENCE'); + + const payload = { + jti: sessionId, + sub: user.id, + email: user.email, + iss: btoa(domain), + aud: btoa(audConstraint), + }; + + const [access, refresh] = await Promise.all([ + this.jwtService.signAsync(payload, { + secret: this.cfg.get('JWT_ACCESS_SECRET'), + expiresIn: this.cfg.get('JWT_ACCESS_EXPIRES_IN'), + }), + this.jwtService.signAsync(payload, { + secret: this.cfg.get('JWT_REFRESH_SECRET'), + expiresIn: this.cfg.get('JWT_REFRESH_EXPIRES_IN'), + }), + ]); + + return { access, refresh }; + } + + async validateToken(token: string, type: 'access' | 'refresh'): Promise { + try { + const accessSecret = this.cfg.get('JWT_ACCESS_SECRET'); + const refreshSecret = this.cfg.get('JWT_REFRESH_SECRET'); + + const secret = type === 'access' ? accessSecret : refreshSecret; + + return this.jwtService.verifyAsync(token, { secret }); + } catch (e) { + return null; + } + } +} diff --git a/src/auth/infrastructure/strategies/bearer.strategy.ts b/src/auth/infrastructure/strategies/bearer.strategy.ts new file mode 100644 index 0000000..c14ed7d --- /dev/null +++ b/src/auth/infrastructure/strategies/bearer.strategy.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import type { JwtPayload } from '@shared/types'; +import { ConfigService } from '@nestjs/config'; +import { PassportStrategy } from '@nestjs/passport'; +import { Strategy, ExtractJwt } from 'passport-jwt'; + +@Injectable() +export class BearerStrategy extends PassportStrategy(Strategy, 'bearer') { + constructor(cfg: ConfigService) { + const audConstraint = cfg.getOrThrow('JWT_AUDIENCE'); + const audience = btoa(audConstraint); + + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: cfg.get('JWT_ACCESS_SECRET'), + issuer: cfg.get('JWT_ISSUER'), + audience, + }); + } + + validate(payload: JwtPayload) { + return payload; + } +} diff --git a/src/auth/infrastructure/strategies/cookie.strategy.ts b/src/auth/infrastructure/strategies/cookie.strategy.ts new file mode 100644 index 0000000..d9334eb --- /dev/null +++ b/src/auth/infrastructure/strategies/cookie.strategy.ts @@ -0,0 +1,38 @@ +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { HttpStatus, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import type { FastifyRequest } from 'fastify'; +import type { JwtPayload } from '@shared/types'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class CookieStrategy extends PassportStrategy(Strategy, 'cookie') { + constructor(configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromExtractors([ + (request: FastifyRequest) => { + const token = request?.cookies?.['refresh']; + return token; + }, + ]), + secretOrKey: configService.get('JWT_REFRESH_SECRET'), + passReqToCallback: true, + }); + } + + validate(_req: FastifyRequest, payload: JwtPayload) { + if (!payload || !payload.jti) { + throw new BaseException( + { + code: 'INVALID_REFRESH_TOKEN', + message: 'Refresh токен невалиден или протух', + details: [{ target: 'auth', reason: 'Payload is missing or jti is invalid' }], + }, + HttpStatus.UNAUTHORIZED, + ); + } + + return payload; + } +} diff --git a/src/auth/infrastructure/strategies/index.ts b/src/auth/infrastructure/strategies/index.ts new file mode 100644 index 0000000..4ea10ce --- /dev/null +++ b/src/auth/infrastructure/strategies/index.ts @@ -0,0 +1,2 @@ +export { BearerStrategy } from './bearer.strategy'; +export { CookieStrategy } from './cookie.strategy'; diff --git a/src/auth/infrastructure/utils/get-device-meta.ts b/src/auth/infrastructure/utils/get-device-meta.ts new file mode 100644 index 0000000..b37f69e --- /dev/null +++ b/src/auth/infrastructure/utils/get-device-meta.ts @@ -0,0 +1,30 @@ +import type { FastifyRequest } from 'fastify'; +import { UAParser } from 'ua-parser-js'; + +export interface DeviceMetadata { + ip: string; + userAgent: string; + browser: string; + os: string; + deviceType: 'mobile' | 'desktop' | 'tablet'; +} + +export function getDeviceMeta(req: FastifyRequest): DeviceMetadata { + const uaString = req.headers['user-agent'] || ''; + const parser = new UAParser(uaString); + const res = parser.getResult(); + + const ip = (req.headers['x-forwarded-for'] as string)?.split(',')[0] || req.ip || '0.0.0.0'; + + let deviceType: 'mobile' | 'desktop' | 'tablet' = 'desktop'; + if (res.device.type === 'mobile') deviceType = 'mobile'; + if (res.device.type === 'tablet') deviceType = 'tablet'; + + return { + ip, + userAgent: uaString, + browser: `${res.browser.name || 'Unknown'} ${res.browser.version || ''}`.trim(), + os: `${res.os.name || 'Unknown'} ${res.os.version || ''}`.trim(), + deviceType, + }; +} diff --git a/src/auth/infrastructure/workers/index.ts b/src/auth/infrastructure/workers/index.ts new file mode 100644 index 0000000..d20e25d --- /dev/null +++ b/src/auth/infrastructure/workers/index.ts @@ -0,0 +1 @@ +export { MailProcessor } from './mail.processor'; diff --git a/src/auth/infrastructure/workers/mail.processor.ts b/src/auth/infrastructure/workers/mail.processor.ts new file mode 100644 index 0000000..3e4a926 --- /dev/null +++ b/src/auth/infrastructure/workers/mail.processor.ts @@ -0,0 +1,72 @@ +import { Processor, WorkerHost } from '@nestjs/bullmq'; +import type { Job } from 'bullmq'; +import { IMailPort } from '@shared/adapters/mail'; +import { Inject } from '@nestjs/common'; +import { RegisterCodeEvent, ResetPasswordEvent } from '../../domain/events'; +import { AuthMailJobs, AuthQueues } from '../../domain/enums'; + +@Processor(AuthQueues.AUTH_MAIL) +export class MailProcessor extends WorkerHost { + constructor( + @Inject('IMailPort') + private readonly mailAdapter: IMailPort, + ) { + super(); + } + + async process(job: Job): Promise; + async process(job: Job): Promise; + async process(job: Job): Promise { + await job.log(`[START] Job ID: ${job.id} | Type: ${job.name}`); + + try { + switch (job.name) { + case AuthMailJobs.SEND_REGISTER_CODE: + await this.sendRegisterCode(job); + break; + case AuthMailJobs.SEND_RESET_PASSWORD: + await this.sendResetPassCode(job); + break; + default: + await job.log(`[WRN] No handler for job: ${job.name}`); + await job.updateProgress(100); + } + + await job.log(`[DONE] Job ${job.id} processed`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + const errorStack = error instanceof Error ? error.stack : ''; + + await job.log(`[FAIL] ${errorMessage}`); + if (errorStack) { + await job.log(errorStack); + } + + throw error; + } + } + + private sendRegisterCode = async (job: Job) => { + const { email, name, otp } = job.data; + + await job.log(`Sending registration code to: ${email}`); + await job.updateProgress(20); + + await this.mailAdapter.sendRegistrationCode(email, name, otp); + + await job.log(`Successfully sent to ${email}`); + await job.updateProgress(100); + }; + + private sendResetPassCode = async (job: Job) => { + const { email, otp } = job.data; + + await job.log(`Sending password reset to: ${email}`); + await job.updateProgress(30); + + await this.mailAdapter.sendResetPasswordCode(email, otp); + + await job.log(`Reset link delivered to ${email}`); + await job.updateProgress(100); + }; +} diff --git a/src/main.ts b/src/main.ts index 13cad38..009bef9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,25 @@ -import { NestFactory } from '@nestjs/core'; +import { bootstrapApp } from '@libs/bootstrap'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - await app.listen(3000); -} -bootstrap(); +bootstrapApp({ + serviceName: 'Tracker Monolit', + appModule: AppModule, + version: 'v1', + defaultPort: 2000, + portEnvKey: 'PORT', + swaggerOptions: { + title: 'Task Tracker API', + description: ` +### Описание +RESTful API сервиса управления задачами (Task Tracker). + +### Поддержка +Для доступа к закрытым методам используйте заголовок Authorization: Bearer token. +По вопросам интеграции обращаться к команде разработки. + `.trim(), + version: '0.1.0', + path: 'docs', + }, + useCors: true, + useCookieParser: true, +}); diff --git a/src/modules/media/dtos/index.ts b/src/modules/media/dtos/index.ts new file mode 100644 index 0000000..9f9e6fe --- /dev/null +++ b/src/modules/media/dtos/index.ts @@ -0,0 +1,2 @@ +export * from './upload-file.dto'; +export * from './upload-file-response.dto'; diff --git a/src/modules/media/dtos/upload-file-response.dto.ts b/src/modules/media/dtos/upload-file-response.dto.ts new file mode 100644 index 0000000..9c6662f --- /dev/null +++ b/src/modules/media/dtos/upload-file-response.dto.ts @@ -0,0 +1,12 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod/v4'; + +export const FileUploadResponseSchema = z.object({ + success: z.boolean().describe('Статус операции'), + url: z.string().describe('URL загруженного файла'), + message: z.string().optional().describe('Сообщение для пользователя'), +}); + +export type FileUploadResponseDto = z.infer; + +export class FileUploadResponse extends createZodDto(FileUploadResponseSchema) {} diff --git a/src/modules/media/dtos/upload-file.dto.ts b/src/modules/media/dtos/upload-file.dto.ts new file mode 100644 index 0000000..32a11f5 --- /dev/null +++ b/src/modules/media/dtos/upload-file.dto.ts @@ -0,0 +1,5 @@ +export class FileUploadDto { + buffer: Buffer; + filename: string; + mimetype: string; +} diff --git a/src/modules/media/index.ts b/src/modules/media/index.ts new file mode 100644 index 0000000..fd5df10 --- /dev/null +++ b/src/modules/media/index.ts @@ -0,0 +1,4 @@ +export { MediaModule } from './media.module'; +export * from './interfaces/team-media.interface'; +export * from './interfaces/user-media.interface'; +export * from './dtos'; diff --git a/src/modules/media/interfaces/team-media.interface.ts b/src/modules/media/interfaces/team-media.interface.ts new file mode 100644 index 0000000..7d151fb --- /dev/null +++ b/src/modules/media/interfaces/team-media.interface.ts @@ -0,0 +1,16 @@ +import type { FileUploadDto, FileUploadResponse } from '../dtos'; + +export const TEAM_MEDIA_TOKEN = 'ITeamMedia'; + +export interface ITeamMedia { + uploadTeamAvatar( + teamId: string, + file: FileUploadDto, + updateFn: (url: string) => Promise, + ): Promise; + uploadTeamBanner( + teamId: string, + file: FileUploadDto, + updateFn: (url: string) => Promise, + ): Promise; +} diff --git a/src/modules/media/interfaces/user-media.interface.ts b/src/modules/media/interfaces/user-media.interface.ts new file mode 100644 index 0000000..55096e8 --- /dev/null +++ b/src/modules/media/interfaces/user-media.interface.ts @@ -0,0 +1,11 @@ +import type { FileUploadDto, FileUploadResponse } from '../dtos'; + +export const USER_MEDIA_TOKEN = 'IUserMedia'; + +export interface IUserMedia { + uploadUserAvatar( + userId: string, + file: FileUploadDto, + updateFn: (url: string) => Promise, + ): Promise; +} diff --git a/src/modules/media/media.module.ts b/src/modules/media/media.module.ts new file mode 100644 index 0000000..8eff7d7 --- /dev/null +++ b/src/modules/media/media.module.ts @@ -0,0 +1,40 @@ +import { Module } from '@nestjs/common'; +import { MediaService } from './media.service'; +import { S3Module } from '@libs/s3'; +import { USER_MEDIA_TOKEN } from './interfaces/user-media.interface'; +import { TEAM_MEDIA_TOKEN } from './interfaces/team-media.interface'; +import { ConfigService } from '@nestjs/config'; + +@Module({ + imports: [ + S3Module.registerAsync({ + inject: [ConfigService], + useFactory: (cfg: ConfigService) => ({ + connection: { + bucket: cfg.getOrThrow('S3_BUCKET_NAME'), + endpoint: cfg.getOrThrow('S3_ENDPOINT'), + region: cfg.getOrThrow('S3_REGION'), + credentials: { + accessKeyId: cfg.getOrThrow('S3_ACCESS_KEY'), + secretAccessKey: cfg.getOrThrow('S3_SECRET_KEY'), + }, + }, + // FOR MINIO COMPARTABLE + config: { forcePathStyle: true }, + }), + }), + ], + providers: [ + MediaService, + { + provide: USER_MEDIA_TOKEN, + useExisting: MediaService, + }, + { + provide: TEAM_MEDIA_TOKEN, + useExisting: MediaService, + }, + ], + exports: [USER_MEDIA_TOKEN, TEAM_MEDIA_TOKEN], +}) +export class MediaModule {} diff --git a/src/modules/media/media.service.ts b/src/modules/media/media.service.ts new file mode 100644 index 0000000..a775a26 --- /dev/null +++ b/src/modules/media/media.service.ts @@ -0,0 +1,85 @@ +import { HttpStatus, Injectable } from '@nestjs/common'; +import { S3Service } from '@libs/s3'; +import type { FileUploadDto, FileUploadResponseDto } from './dtos'; +import { IUserMedia } from './interfaces/user-media.interface'; +import { ITeamMedia } from './interfaces/team-media.interface'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class MediaService implements IUserMedia, ITeamMedia { + constructor(private readonly s3: S3Service) {} + + private async uploadAndLink( + file: FileUploadDto, + folder: string, + updateDbFn: (url: string) => Promise, + ): Promise { + const url = await this.s3.uploadFile(file.buffer, file.filename, file.mimetype, folder); + + try { + const isUpdated = await updateDbFn(url); + + if (!isUpdated) { + throw new BaseException( + { + code: 'ENTITY_NOT_FOUND', + message: 'Сущность не найдена, обновление отменено', + details: [ + { + target: 'id', + message: 'Record with provided ID does not exist in database', + }, + ], + }, + HttpStatus.NOT_FOUND, + ); + } + + return { success: true, url }; + } catch (error) { + await this.s3.deleteFile(url); + + if (error instanceof BaseException) { + throw error; + } + + throw new BaseException( + { + code: 'MEDIA_SAVE_FAILED', + message: 'Ошибка при сохранении медиа-данных', + details: [ + { + reason: + error instanceof Error ? error.message : 'Unknown database error', + }, + ], + }, + HttpStatus.BAD_REQUEST, + ); + } + } + + public async uploadUserAvatar( + userId: string, + file: FileUploadDto, + updateFn: (url: string) => Promise, + ) { + return this.uploadAndLink(file, `users/${userId}/avatars`, updateFn); + } + + public async uploadTeamAvatar( + teamId: string, + file: FileUploadDto, + updateFn: (url: string) => Promise, + ) { + return this.uploadAndLink(file, `teams/${teamId}/avatars`, updateFn); + } + + public async uploadTeamBanner( + teamId: string, + file: FileUploadDto, + updateFn: (url: string) => Promise, + ) { + return this.uploadAndLink(file, `teams/${teamId}/banners`, updateFn); + } +} diff --git a/src/modules/projects/commands/find-project.command.ts b/src/modules/projects/commands/find-project.command.ts new file mode 100644 index 0000000..099e8eb --- /dev/null +++ b/src/modules/projects/commands/find-project.command.ts @@ -0,0 +1,90 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { IProjectsRepository } from '../repository'; +import { FindTeamMemberCommand } from '@core/modules/teams'; +import { createHash } from 'crypto'; +import type { Project } from '../entities'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class FindProjectCommand { + constructor( + @Inject('IProjectsRepository') + private readonly projectsRepo: IProjectsRepository, + private readonly findTeamMemberCommand: FindTeamMemberCommand, + ) {} + + public async execute(projectId: string, userId?: string, shareToken?: string) { + const project = await this.projectsRepo.findOne(projectId); + + if (!project) { + throw new BaseException( + { + code: 'PROJECT_NOT_FOUND', + message: 'Проект не найден или доступ ограничен', + details: [{ target: 'projectId', value: projectId }], + }, + HttpStatus.NOT_FOUND, + ); + } + + if (shareToken) { + return this.findPublic(project, shareToken); + } + + return this.findPrivate(project, userId); + } + + private findPrivate = async (project: Project, userId?: string) => { + if (!userId) { + throw new BaseException( + { + code: 'AUTH_REQUIRED', + message: 'Для доступа к приватному проекту нужна авторизация', + }, + HttpStatus.UNAUTHORIZED, + ); + } + + const member = await this.findTeamMemberCommand.execute(project.teamId, userId); + + if (!member) { + throw new BaseException( + { + code: 'ACCESS_DENIED', + message: 'У вас нет прав для просмотра этого проекта', + details: [{ target: 'teamId', value: project.teamId }], + }, + HttpStatus.FORBIDDEN, + ); + } + + return { project, member }; + }; + + private findPublic = async (project: Project, token: string) => { + if (project.visibility !== 'public') { + throw new BaseException( + { + code: 'PROJECT_NOT_PUBLIC', + message: 'Этот проект не является публичным', + }, + HttpStatus.FORBIDDEN, + ); + } + + const hashedToken = createHash('sha256').update(token).digest('hex'); + const isValidToken = await this.projectsRepo.hasValidShareToken(project.id, hashedToken); + + if (!isValidToken) { + throw new BaseException( + { + code: 'SHARE_LINK_INVALID', + message: 'Ссылка недействительна или срок её действия истек', + }, + HttpStatus.GONE, + ); + } + + return { project, member: null }; + }; +} diff --git a/src/modules/projects/commands/index.ts b/src/modules/projects/commands/index.ts new file mode 100644 index 0000000..d79925b --- /dev/null +++ b/src/modules/projects/commands/index.ts @@ -0,0 +1 @@ +export { FindProjectCommand } from './find-project.command'; diff --git a/src/modules/projects/controller/index.ts b/src/modules/projects/controller/index.ts new file mode 100644 index 0000000..19a0d95 --- /dev/null +++ b/src/modules/projects/controller/index.ts @@ -0,0 +1 @@ +export { ProjectsController } from './projects.controller'; diff --git a/src/modules/projects/controller/projects.controller.ts b/src/modules/projects/controller/projects.controller.ts new file mode 100644 index 0000000..c3e41ba --- /dev/null +++ b/src/modules/projects/controller/projects.controller.ts @@ -0,0 +1,89 @@ +import { ApiBaseController, GetUserId, Public } from '@shared/decorators'; +import { ProjectsService } from '../services'; +import { Body, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; +import { + ArchiveProjectSwagger, + CreateProjectSwagger, + CreateShareTokenSwagger, + FindAllProjectsSwagger, + FindOneProjectSwagger, + RemoveProjectSwagger, + UpdateProjectSwagger, +} from './projects.swagger'; +import { CreateProjectDto, CreateShareTokenDto, UpdateProjectDto } from '../dtos'; +import { ProjectStatus } from '../entities'; + +@ApiBaseController('teams/:slug/projects', 'Team Projects', true) +export class ProjectsController { + constructor(private readonly facade: ProjectsService) {} + + @Get() + @FindAllProjectsSwagger() + async findAll(@Param('slug') slug: string, @GetUserId() userId: string) { + return this.facade.findByTeam(slug, userId); + } + + @Get(':id') + @Public() + @FindOneProjectSwagger() + async getOne( + @Param('id') id: string, + @Param('slug') slug: string, + @GetUserId() userId?: string, + @Query('token') token?: string, + ) { + return this.facade.findOne(id, slug, userId, token); + } + + @Post(':id/share') + @CreateShareTokenSwagger() + async generateShareToken( + @Param('id') id: string, + @Param('slug') slug: string, + @GetUserId() userId: string, + @Body() dto: CreateShareTokenDto, + ) { + return this.facade.generateToken(id, slug, userId, dto); + } + + @Post(':id/archive') + @ArchiveProjectSwagger() + async archive( + @Param('id') id: string, + @Param('slug') slug: string, + @GetUserId() userId: string, + ) { + return this.facade.setStatus(id, slug, userId, ProjectStatus.Archived); + } + + @Post() + @CreateProjectSwagger() + async create( + @Param('slug') slug: string, + @GetUserId() userId: string, + @Body() dto: CreateProjectDto, + ) { + return this.facade.create(userId, slug, dto); + } + + @Patch(':id') + @UpdateProjectSwagger() + async update( + @Param('id') id: string, + @Param('slug') slug: string, + @GetUserId() userId: string, + @Body() dto: UpdateProjectDto, + ) { + return this.facade.update(id, slug, userId, dto); + } + + @Delete(':id') + @RemoveProjectSwagger() + async remove( + @Param('id') id: string, + @Param('slug') slug: string, + @GetUserId() userId: string, + ) { + return this.facade.delete(id, slug, userId); + } +} diff --git a/src/modules/projects/controller/projects.swagger.ts b/src/modules/projects/controller/projects.swagger.ts new file mode 100644 index 0000000..09f184c --- /dev/null +++ b/src/modules/projects/controller/projects.swagger.ts @@ -0,0 +1,135 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiOperation, ApiBody, ApiResponse, ApiParam } from '@nestjs/swagger'; +import { ActionResponse } from '@shared/dtos'; +import { ApiValidationError, ApiUnauthorized, ApiForbidden, ApiNotFound } from '@shared/error'; +import { + CreateProjectDto, + CreateProjectResponse, + CreateShareTokenDto, + UpdateProjectDto, +} from '../dtos'; + +export const CreateProjectSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Создать новый проект в команде' }), + ApiParam({ name: 'slug', type: 'string' }), + ApiBody({ type: CreateProjectDto.Output }), + ApiResponse({ + status: 201, + description: 'Проект успешно создан', + type: CreateProjectResponse.Output, + }), + ApiValidationError(), + ApiUnauthorized(), + ApiForbidden(), + ); + +export const FindAllProjectsSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Получить список всех проектов команды' }), + ApiParam({ name: 'slug', type: 'string' }), + ApiResponse({ + status: 200, + description: 'Список проектов получен', + type: [Object], + }), + ApiUnauthorized(), + ); + +export const FindOneProjectSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Получить детальную информацию о проекте' }), + ApiParam({ + name: 'id', + description: 'CUID проекта', + type: 'string', + example: 'clv123456', + }), + ApiResponse({ status: 200, type: Object }), + ApiNotFound('Проект не найден'), + ApiUnauthorized(), + ); + +export const UpdateProjectSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Обновить информацию о проекте' }), + ApiParam({ + name: 'id', + description: 'CUID проекта', + type: 'string', + example: 'clv123456', + }), + ApiBody({ type: UpdateProjectDto.Output }), + ApiResponse({ status: 200, description: 'Проект обновлен', type: ActionResponse.Output }), + ApiValidationError(), + ApiNotFound(), + ApiUnauthorized(), + ); + +export const RemoveProjectSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Архивировать (удалить) проект' }), + ApiParam({ + name: 'id', + description: 'CUID проекта', + type: 'string', + example: 'clv123456', + }), + ApiResponse({ status: 200, description: 'Проект удален', type: ActionResponse.Output }), + ApiNotFound(), + ApiUnauthorized(), + ); + +export const ArchiveProjectSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Перевести проект в статус архива' }), + ApiParam({ + name: 'id', + description: 'CUID проекта', + type: 'string', + example: 'clv123456', + }), + ApiResponse({ status: 200, description: 'Статус обновлен', type: ActionResponse.Output }), + ApiUnauthorized(), + ); + +export const GetProjectByTokenSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Получить проект по публичному токену' }), + ApiParam({ name: 'token', description: 'Токен доступа', type: 'string' }), + ApiResponse({ status: 200, type: Object }), + ApiNotFound('Токен недействителен'), + ); + +export const CreateShareTokenSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Сгенерировать публичную ссылку', + description: + 'Создает защищенный токен доступа к проекту. Если expiresAt не указан, по умолчанию ставится доступ на 3 месяца.', + }), + ApiParam({ + name: 'slug', + description: 'Slug команды', + type: 'string', + }), + ApiParam({ + name: 'id', + description: 'CUID проекта', + type: 'string', + example: 'clv123456', + }), + ApiBody({ + type: CreateShareTokenDto.Output, + description: 'Настройки срока действия ссылки', + }), + ApiResponse({ + status: 201, + description: 'Токен успешно создан', + type: ActionResponse.Output, + }), + ApiNotFound('Проект не найден в этой команде'), + ApiValidationError('Некорректная дата или параметры'), + ApiUnauthorized(), + ApiForbidden('У вас нет прав для создания ссылки для этого проекта'), + ); diff --git a/src/modules/projects/dtos/index.ts b/src/modules/projects/dtos/index.ts new file mode 100644 index 0000000..359d3f9 --- /dev/null +++ b/src/modules/projects/dtos/index.ts @@ -0,0 +1,6 @@ +export { + CreateProjectDto, + UpdateProjectDto, + CreateProjectResponse, + CreateShareTokenDto, +} from './projects.dto'; diff --git a/src/modules/projects/dtos/projects.dto.ts b/src/modules/projects/dtos/projects.dto.ts new file mode 100644 index 0000000..042444f --- /dev/null +++ b/src/modules/projects/dtos/projects.dto.ts @@ -0,0 +1,54 @@ +import { z } from 'zod/v4'; +import { createZodDto } from 'nestjs-zod'; +import { ProjectStatus } from '../entities'; +import { ActionResponseSchema } from '@shared/dtos'; + +export const CreateProjectSchema = z.object({ + name: z + .string() + .min(1, 'Название проекта не может быть пустым') + .max(100, 'Название не должно превышать 100 символов'), + key: z + .string() + .min(2, 'Ключ проекта должен быть от 2 до 10 символов') + .max(10) + .regex(/^[A-Z0-9]+$/, 'Ключ должен содержать только заглавные латинские буквы и цифры'), + description: z.string().max(2000, 'Описание слишком длинное').optional().nullable(), + icon: z.string().optional().nullable(), + color: z + .string() + .regex(/^#[A-Fa-f0-9]{6}$/, 'Цвет должен быть в формате HEX (например, #FFFFFF)') + .optional(), + visibility: z.enum(['public', 'private']).default('public'), +}); + +export class CreateProjectDto extends createZodDto(CreateProjectSchema) {} + +export const UpdateProjectSchema = CreateProjectSchema.extend({ + status: z.enum([ProjectStatus.Active, ProjectStatus.Archived]).optional(), + isPublic: z.boolean().optional(), +}) + .partial() + .refine((data) => Object.keys(data).length > 0, { + error: 'Необходимо передать хотя бы одно поле для обновления', + abort: true, + }); + +export class UpdateProjectDto extends createZodDto(UpdateProjectSchema) {} + +const CreateProjectsResponseSchema = ActionResponseSchema.extend({ + projectId: z.string().describe('Уникальный идентификатор проекта в системе'), +}); + +export class CreateProjectResponse extends createZodDto(CreateProjectsResponseSchema) {} + +export const CreateShareTokenSchema = z.object({ + ttl: z + .string() + .datetime() + .optional() + .nullable() + .describe('Дата истечения ссылки. Если не указана — ставится дефолт 3 месяца'), +}); + +export class CreateShareTokenDto extends createZodDto(CreateShareTokenSchema) {} diff --git a/src/modules/projects/entities/entities.domain.ts b/src/modules/projects/entities/entities.domain.ts new file mode 100644 index 0000000..6170b73 --- /dev/null +++ b/src/modules/projects/entities/entities.domain.ts @@ -0,0 +1,28 @@ +import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; +import { projects, projectShares } from './projects.entity'; + +export enum ProjectStatus { + Active = 'active', + Archived = 'archived', + Template = 'template', +} + +export enum ProjectVisibility { + Public = 'public', + Private = 'private', +} + +export type Project = InferSelectModel; +export type NewProject = InferInsertModel; +export interface ProjectSettings { + allowGuestComments?: boolean; + defaultAssigneeId?: string; + showTaskNumbers?: boolean; +} + +export type ProjectWithTypedSettings = Omit & { + settings: ProjectSettings; +}; + +export type ProjectShare = InferSelectModel; +export type NewProjectShare = InferInsertModel; diff --git a/src/modules/projects/entities/enums.ts b/src/modules/projects/entities/enums.ts new file mode 100644 index 0000000..5cfc624 --- /dev/null +++ b/src/modules/projects/entities/enums.ts @@ -0,0 +1,8 @@ +import { baseSchema } from '@shared/entities'; + +export const projectStatusEnum = baseSchema.enum('project_status', [ + 'active', + 'archived', + 'template', +]); +export const projectVisibilityEnum = baseSchema.enum('project_visibility', ['public', 'private']); diff --git a/src/modules/projects/entities/index.ts b/src/modules/projects/entities/index.ts new file mode 100644 index 0000000..4dd5b24 --- /dev/null +++ b/src/modules/projects/entities/index.ts @@ -0,0 +1,3 @@ +export { projects, projectShares } from './projects.entity'; +export { projectStatusEnum, projectVisibilityEnum } from './enums'; +export * from './entities.domain'; diff --git a/src/modules/projects/entities/projects.entity.ts b/src/modules/projects/entities/projects.entity.ts new file mode 100644 index 0000000..2eccc18 --- /dev/null +++ b/src/modules/projects/entities/projects.entity.ts @@ -0,0 +1,60 @@ +import { text, varchar, timestamp, jsonb, integer, uniqueIndex, index } from 'drizzle-orm/pg-core'; +import { baseSchema, teams, users } from '@shared/entities'; +import { createId } from '@paralleldrive/cuid2'; +import { isNull } from 'drizzle-orm'; +import { projectStatusEnum, projectVisibilityEnum } from './enums'; + +export const projects = baseSchema.table( + 'projects', + { + id: text('id') + .primaryKey() + .$defaultFn(() => createId()), + teamId: text('team_id') + .references(() => teams.id, { onDelete: 'cascade' }) + .notNull(), + key: varchar('key', { length: 10 }).notNull(), + name: varchar('name', { length: 100 }).notNull(), + description: text('description'), + icon: varchar('icon', { length: 255 }), + color: varchar('color', { length: 7 }), + status: projectStatusEnum('status').default('active').notNull(), + taskSequence: integer('task_sequence').default(0).notNull(), + ownerId: text('owner_id').references(() => users.id, { onDelete: 'set null' }), + visibility: projectVisibilityEnum('visibility').default('public').notNull(), + settings: jsonb('settings').default({}), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), + deletedAt: timestamp('deleted_at'), + }, + (t) => ({ + uniqueTeamKey: uniqueIndex('project_team_key_idx') + .on(t.teamId, t.key) + .where(isNull(t.deletedAt)), + uniqueTeamName: uniqueIndex('project_team_name_idx') + .on(t.teamId, t.name) + .where(isNull(t.deletedAt)), + ownerIdx: index('project_owner_id_idx').on(t.ownerId), + teamIdx: index('project_team_id_idx').on(t.teamId), + }), +); + +export const projectShares = baseSchema.table( + 'project_shares', + { + id: text('id') + .primaryKey() + .$defaultFn(() => createId()), + projectId: text('project_id') + .notNull() + .references(() => projects.id, { onDelete: 'cascade' }), + token: text('token').notNull().unique(), + expiresAt: timestamp('expires_at', { withTimezone: true }), + createdBy: text('created_by').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), + }, + (table) => ({ + tokenIdx: index('token_idx').on(table.token), + projectIdx: index('project_share_project_id_idx').on(table.projectId), + }), +); diff --git a/src/modules/projects/index.ts b/src/modules/projects/index.ts new file mode 100644 index 0000000..f17852e --- /dev/null +++ b/src/modules/projects/index.ts @@ -0,0 +1 @@ +export { ProjectsModule } from './projects.module'; diff --git a/src/modules/projects/mappers/index.ts b/src/modules/projects/mappers/index.ts new file mode 100644 index 0000000..7f5f566 --- /dev/null +++ b/src/modules/projects/mappers/index.ts @@ -0,0 +1 @@ +export { ProjectsMapper } from './projects.mapper'; diff --git a/src/modules/projects/mappers/projects.mapper.ts b/src/modules/projects/mappers/projects.mapper.ts new file mode 100644 index 0000000..e63220e --- /dev/null +++ b/src/modules/projects/mappers/projects.mapper.ts @@ -0,0 +1,63 @@ +import type { RawMemberRow } from '@core/modules/teams/repository'; +import type { Project } from '@shared/entities'; +import { ROLE_PRIORITY } from '@shared/constants'; + +export class ProjectsMapper { + public static toDetailResponse(project: Project, member?: RawMemberRow, token?: string) { + const { + id, + key, + name, + status, + description, + color, + icon, + taskSequence, + createdAt, + updatedAt, + visibility, + settings, + } = project; + + const rolePriority = member ? ROLE_PRIORITY[member.role] : -1; + + return { + id, + key, + name, + status, + description, + visuals: { + color: color ?? '#3b82f6', + icon, + }, + meta: { + taskSequence, + createdAt, + updatedAt, + }, + access: { + visibility, + canEdit: rolePriority >= ROLE_PRIORITY.moderator, + canDelete: rolePriority >= ROLE_PRIORITY.admin, + shareUrl: visibility === 'public' && token ? `/share/${token}` : null, + }, + settings: settings || {}, + }; + } + + public static toListResponse(project: Project, member: RawMemberRow) { + const { id, key, name, status, color, icon, createdAt } = project; + + return { + id, + key, + name, + status, + color: color ?? '#3b82f6', + icon, + createdAt, + canEdit: ROLE_PRIORITY[member.role] >= ROLE_PRIORITY.moderator, + }; + } +} diff --git a/src/modules/projects/projects.module.ts b/src/modules/projects/projects.module.ts new file mode 100644 index 0000000..daaeac6 --- /dev/null +++ b/src/modules/projects/projects.module.ts @@ -0,0 +1,19 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { ProjectsService } from './services'; +import { ProjectsController } from './controller'; +import { ProjectsRepository } from './repository'; +import { TeamsModule } from '../teams'; +import { FindProjectCommand } from './commands'; + +const REPOSITORY = { + provide: 'IProjectsRepository', + useClass: ProjectsRepository, +}; + +@Module({ + imports: [forwardRef(() => TeamsModule)], + controllers: [ProjectsController], + providers: [REPOSITORY, FindProjectCommand, ProjectsService], + exports: [FindProjectCommand], +}) +export class ProjectsModule {} diff --git a/src/modules/projects/repository/index.ts b/src/modules/projects/repository/index.ts new file mode 100644 index 0000000..8aec19a --- /dev/null +++ b/src/modules/projects/repository/index.ts @@ -0,0 +1,2 @@ +export { ProjectsRepository } from './projects.repository'; +export { IProjectsRepository } from './projects.repository.interface'; diff --git a/src/modules/projects/repository/projects.repository.interface.ts b/src/modules/projects/repository/projects.repository.interface.ts new file mode 100644 index 0000000..58fc8cf --- /dev/null +++ b/src/modules/projects/repository/projects.repository.interface.ts @@ -0,0 +1,12 @@ +import type { NewProject, NewProjectShare, Project } from '../entities'; + +export interface IProjectsRepository { + create(data: NewProject): Promise<{ result: boolean; id: string }>; + update(id: string, data: Partial): Promise; + delete(id: string): Promise; + findOne(id: string): Promise; + findByTeam(teamId: string): Promise; + createShare(data: NewProjectShare): Promise; + hasValidShareToken(id: string, token: string): Promise; + revokeAllShares(projectId: string): Promise; +} diff --git a/src/modules/projects/repository/projects.repository.ts b/src/modules/projects/repository/projects.repository.ts new file mode 100644 index 0000000..a4f6750 --- /dev/null +++ b/src/modules/projects/repository/projects.repository.ts @@ -0,0 +1,104 @@ +import { DATABASE_SERVICE, DatabaseService } from '@libs/database'; +import { Injectable, Inject } from '@nestjs/common'; +import * as schema from '../entities'; +import { IProjectsRepository } from './projects.repository.interface'; +import { and, eq, gt, isNull, or } from 'drizzle-orm'; + +@Injectable() +export class ProjectsRepository implements IProjectsRepository { + constructor( + @Inject(DATABASE_SERVICE) + private readonly db: DatabaseService, + ) {} + + public create = async (data: schema.NewProject) => { + const result = await this.db + .insert(schema.projects) + .values(data) + .returning({ id: schema.projects.id }); + + return { result: result.length > 0, id: result[0].id }; + }; + + public update = async (id: string, data: Partial) => { + const result = await this.db + .update(schema.projects) + .set({ ...data, updatedAt: new Date() }) + .where(eq(schema.projects.id, id)) + .returning({ id: schema.projects.id }); + + return result.length > 0; + }; + + public delete = async (id: string) => { + const result = await this.db + .update(schema.projects) + .set({ deletedAt: new Date() }) + .where(eq(schema.projects.id, id)) + .returning({ id: schema.projects.id }); + + return result.length > 0; + }; + + public findOne = async (id: string) => { + const [project] = await this.db + .select() + .from(schema.projects) + .where(and(eq(schema.projects.id, id), isNull(schema.projects.deletedAt))); + + if (!project) return null; + + return project; + }; + + public findByTeam = async (teamId: string) => { + return this.db + .select() + .from(schema.projects) + .where(and(eq(schema.projects.teamId, teamId), isNull(schema.projects.deletedAt))); + }; + + public createShare = async (data: schema.NewProjectShare) => { + const [result] = await this.db + .insert(schema.projectShares) + .values(data) + .onConflictDoUpdate({ + target: schema.projectShares.token, + set: { + expiresAt: data.expiresAt, + token: data.token, + }, + }) + .returning({ id: schema.projectShares.id }); + + return !!result; + }; + + public hasValidShareToken = async (id: string, token: string) => { + const [result] = await this.db + .select() + .from(schema.projectShares) + .where( + and( + eq(schema.projectShares.projectId, id), + eq(schema.projectShares.token, token), + or( + isNull(schema.projectShares.expiresAt), + gt(schema.projectShares.expiresAt, new Date()), + ), + ), + ) + .limit(1); + + return !!result; + }; + + public revokeAllShares = async (projectId: string) => { + const result = await this.db + .delete(schema.projectShares) + .where(eq(schema.projectShares.projectId, projectId)) + .returning({ id: schema.projectShares.id }); + + return result.length > 0; + }; +} diff --git a/src/modules/projects/services/index.ts b/src/modules/projects/services/index.ts new file mode 100644 index 0000000..e46b58b --- /dev/null +++ b/src/modules/projects/services/index.ts @@ -0,0 +1 @@ +export { ProjectsService } from './projects.service'; diff --git a/src/modules/projects/services/projects.service.ts b/src/modules/projects/services/projects.service.ts new file mode 100644 index 0000000..4ea0667 --- /dev/null +++ b/src/modules/projects/services/projects.service.ts @@ -0,0 +1,327 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { IProjectsRepository } from '../repository'; +import type { CreateProjectDto, CreateShareTokenDto, UpdateProjectDto } from '../dtos'; +import { FindTeamCommand, FindTeamMemberCommand } from '@core/modules/teams'; +import { ROLE_PRIORITY } from '@shared/constants'; +import { ProjectStatus } from '../entities'; +import { ProjectsMapper } from '../mappers'; +import { createHash, randomBytes } from 'crypto'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class ProjectsService { + constructor( + @Inject('IProjectsRepository') + private readonly projectsRepo: IProjectsRepository, + private readonly findTeamCommand: FindTeamCommand, + private readonly findTeamMemberCommand: FindTeamMemberCommand, + ) {} + + public create = async (userId: string, slug: string, dto: CreateProjectDto) => { + const { team } = await this.ensureTeamAccess(slug, userId, 'admin'); + + const data = { + ...dto, + teamId: team.id, + ownerId: userId, + key: dto.key.toUpperCase(), + status: ProjectStatus.Active, + }; + + const { result, id } = await this.projectsRepo.create(data); + + return { + success: result, + message: `Проект ${dto.name} успешно создан`, + projectId: id, + }; + }; + + public generateToken = async ( + id: string, + slug: string, + userId: string, + dto: CreateShareTokenDto, + ) => { + const project = await this.validateAccess(id, slug, userId); + + let expiresAt: Date; + + if (dto.ttl) { + expiresAt = new Date(dto.ttl); + + if (expiresAt <= new Date()) { + throw new BaseException( + { + code: 'INVALID_EXPIRATION', + message: 'Дата истечения не может быть в прошлом', + details: [ + { target: 'ttl', message: 'Expiration date is behind current time' }, + ], + }, + HttpStatus.BAD_REQUEST, + ); + } + } else { + expiresAt = new Date(); + expiresAt.setMonth(expiresAt.getMonth() + 3); + } + + const rawToken = this.generateSecureToken(); + + const isSaved = await this.projectsRepo.createShare({ + projectId: project.id, + token: this.hash(rawToken), + expiresAt, + createdBy: userId, + }); + + if (!isSaved) { + throw new BaseException( + { + code: 'SHARE_CREATE_FAILED', + message: 'Не удалось сгенерировать ссылку доступа', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + const durationMsg = dto.ttl + ? `закроется ${expiresAt.toLocaleDateString('ru-RU')}` + : 'бессрочна (на 3 месяца по умолчанию)'; + + return { + success: true, + message: `Ссылка для проекта «${project.name}» создана и ${durationMsg}`, + payload: { + token: rawToken, + isYourself: !!dto, + expiresAt: expiresAt.toISOString(), + }, + }; + }; + + public delete = async (id: string, slug: string, userId: string) => { + const project = await this.validateAccess(id, slug, userId); + const result = await this.projectsRepo.delete(project.id); + + if (!result) { + throw new BaseException( + { + code: 'DELETE_FAILED', + message: 'Не удалось удалить проект', + }, + HttpStatus.SERVICE_UNAVAILABLE, + ); + } + + return { + success: result, + message: result + ? `Проект ${project.name} успешно перемещен в корзину` + : 'Не удалось удалить проект, попробуйте позже', + }; + }; + + public update = async (id: string, slug: string, userId: string, dto: UpdateProjectDto) => { + const project = await this.validateAccess(id, slug, userId); + const { isPublic, key, ...data } = dto; + + const result = await this.projectsRepo.update(project.id, { + ...data, + ...(key && { key: key.toUpperCase() }), + ...(typeof isPublic === 'boolean' && { + visibility: isPublic ? 'public' : 'private', + }), + }); + + if (!result) { + throw new BaseException( + { + code: 'UPDATE_FAILED', + message: + 'Изменения не были применены. Возможно, данные идентичны текущим или проект недоступен', + }, + HttpStatus.BAD_REQUEST, + ); + } + + return { + success: result, + message: result ? 'Настройки проекта успешно обновлены' : 'Изменения не были применены', + }; + }; + + public findOne = async (id: string, slug: string, userId: string, token: string) => { + const project = await this.projectsRepo.findOne(id); + + if (!project) { + throw new BaseException( + { + code: 'PROJECT_NOT_FOUND', + message: 'Проект не найден', + }, + HttpStatus.NOT_FOUND, + ); + } + + if (token) { + const hashedToken = this.hash(token); + const isValidAccess = await this.projectsRepo.hasValidShareToken( + project.id, + hashedToken, + ); + + if (!isValidAccess) { + throw new BaseException( + { + code: 'INVALID_TOKEN', + message: 'Ссылка недействительна или срок её действия истек', + }, + HttpStatus.GONE, + ); + } + + return ProjectsMapper.toDetailResponse(project, null, token); + } + + if (!userId) { + throw new BaseException( + { code: 'AUTH_REQUIRED', message: 'Требуется авторизация' }, + HttpStatus.UNAUTHORIZED, + ); + } + + const { member, team } = await this.ensureTeamAccess(slug, userId, 'viewer'); + + if (team.id !== project.teamId) { + throw new BaseException( + { code: 'PROJECT_MISMATCH', message: 'Проект не принадлежит этой команде' }, + HttpStatus.BAD_REQUEST, + ); + } + + return ProjectsMapper.toDetailResponse(project, member); + }; + + public findByTeam = async (slug: string, userId: string) => { + const { team, member } = await this.ensureTeamAccess(slug, userId, 'viewer'); + const projects = await this.projectsRepo.findByTeam(team.id); + + return { + team: { + id: team.id, + name: team.name, + slug: team.slug, + role: member.role, + }, + items: projects.map((p) => ProjectsMapper.toListResponse(p, member)), + meta: { + total: projects.length, + }, + }; + }; + + public setStatus = async (id: string, slug: string, userId: string, status: ProjectStatus) => { + const project = await this.validateAccess(id, slug, userId); + const result = await this.projectsRepo.update(project.id, { status }); + + if (!result) { + throw new BaseException( + { + code: 'STATUS_UPDATE_FAILED', + message: 'Не удалось обновить статус проекта', + details: [{ target: 'status', value: status }], + }, + HttpStatus.SERVICE_UNAVAILABLE, + ); + } + + const messages: Record = { + archived: `Проект «${project.name}» успешно архивирован`, + active: `Проект «${project.name}» теперь активен`, + template: `Проект «${project.name}» успешно сохранен как шаблон`, + }; + + return { + success: result, + message: messages[status] || `Статус проекта «${project.name}» изменен`, + }; + }; + + private async ensureTeamAccess( + slug: string, + userId: string, + minRole: keyof typeof ROLE_PRIORITY = 'viewer', + ) { + const team = await this.findTeamCommand.execute(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + }, + HttpStatus.NOT_FOUND, + ); + } + + const member = await this.findTeamMemberCommand.execute(team.id, userId); + if (!member) { + throw new BaseException( + { + code: 'NOT_TEAM_MEMBER', + message: 'Вы не являетесь участником этой команды', + }, + HttpStatus.FORBIDDEN, + ); + } + + if (ROLE_PRIORITY[member.role] < ROLE_PRIORITY[minRole]) { + throw new BaseException( + { + code: 'INSUFFICIENT_PERMISSIONS', + message: `Только ${minRole} и выше могут выполнять это действие`, + details: [ + { + target: 'role', + message: `Current role: ${member.role}, Required: ${minRole}`, + }, + ], + }, + HttpStatus.FORBIDDEN, + ); + } + + return { team, member }; + } + + private async validateAccess( + id: string, + slug: string, + userId: string, + minRole: keyof typeof ROLE_PRIORITY = 'admin', + ) { + const { team } = await this.ensureTeamAccess(slug, userId, minRole); + + const project = await this.projectsRepo.findOne(id); + if (!project || project.teamId !== team.id) { + throw new BaseException( + { + code: 'PROJECT_NOT_FOUND', + message: 'Проект не найден в этой команде', + }, + HttpStatus.NOT_FOUND, + ); + } + + return project; + } + + private generateSecureToken(): string { + return `st_${randomBytes(32).toString('hex')}`; + } + + private hash(token: string): string { + return createHash('sha256').update(token).digest('hex'); + } +} diff --git a/src/modules/teams/commands/find-member.command.ts b/src/modules/teams/commands/find-member.command.ts new file mode 100644 index 0000000..ee15c5e --- /dev/null +++ b/src/modules/teams/commands/find-member.command.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ITeamsRepository } from '../repository'; + +@Injectable() +export class FindTeamMemberCommand { + constructor( + @Inject('ITeamsRepository') + private readonly repository: ITeamsRepository, + ) {} + + async execute(teamId: string, userId: string) { + return this.repository.findMember(teamId, userId); + } +} diff --git a/src/modules/teams/commands/find-team.command.ts b/src/modules/teams/commands/find-team.command.ts new file mode 100644 index 0000000..f9d11a2 --- /dev/null +++ b/src/modules/teams/commands/find-team.command.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ITeamsRepository } from '../repository'; + +@Injectable() +export class FindTeamCommand { + constructor( + @Inject('ITeamsRepository') + private readonly repository: ITeamsRepository, + ) {} + + async execute(slug: string) { + return this.repository.findBySlug(slug); + } +} diff --git a/src/modules/teams/commands/index.ts b/src/modules/teams/commands/index.ts new file mode 100644 index 0000000..2292e4a --- /dev/null +++ b/src/modules/teams/commands/index.ts @@ -0,0 +1,2 @@ +export { FindTeamMemberCommand } from './find-member.command'; +export { FindTeamCommand } from './find-team.command'; diff --git a/src/modules/teams/controller/index.ts b/src/modules/teams/controller/index.ts new file mode 100644 index 0000000..ac78b0a --- /dev/null +++ b/src/modules/teams/controller/index.ts @@ -0,0 +1,5 @@ +export { MeController } from './me.controller'; +export { TeamsController } from './teams.controller'; +export { TeamsMembersController } from './members.controller'; +export { TeamsSettingsController } from './settings.controller'; +export { TeamsInvitationsController } from './invitations.controller'; diff --git a/src/modules/teams/controller/invitations.controller.ts b/src/modules/teams/controller/invitations.controller.ts new file mode 100644 index 0000000..a4df155 --- /dev/null +++ b/src/modules/teams/controller/invitations.controller.ts @@ -0,0 +1,71 @@ +import { Body, Get, Param, Delete, Patch, Post } from '@nestjs/common'; +import { ApiBaseController, GetUser, GetUserId } from '@shared/decorators'; +import { TeamInvitationsService } from '../services'; +import { + AcceptInviteSwagger, + DeleteTeamInvitationSwagger, + GetTeamInvitationSwagger, + GetTeamInvitationsSwagger, + InviteMemberSwagger, + UpdateTeamInvitationSwagger, +} from './teams.swagger'; +import type { JwtPayload } from '@shared/types'; +import { InviteMemberDto, UpdateInvitationDto } from '../dtos'; + +@ApiBaseController('teams/:slug/invitations', 'Teams Invitations', true) +export class TeamsInvitationsController { + constructor(private readonly facade: TeamInvitationsService) {} + + @Get() + @GetTeamInvitationsSwagger() + async getAll(@Param('slug') slug: string, @GetUserId() userId: string) { + return this.facade.getInvitations(slug, userId); + } + + @Get(':code') + @GetTeamInvitationSwagger() + async getOne( + @Param('slug') slug: string, + @Param('code') code: string, + @GetUserId() userId: string, + ) { + return this.facade.getInvitation(slug, code, userId); + } + + @Post() + @InviteMemberSwagger() + async invite( + @Param('slug') slug: string, + @GetUserId() inviterId: string, + @Body() dto: InviteMemberDto, + ) { + return this.facade.invite(slug, inviterId, dto); + } + + @Post(':code/accept') + @AcceptInviteSwagger() + async accept(@Param('code') code: string, @GetUser() user: JwtPayload) { + return this.facade.acceptInvite(code, user.sub, user.email); + } + + @Patch(':code') + @UpdateTeamInvitationSwagger() + async update( + @Param('slug') slug: string, + @Param('code') code: string, + @GetUserId() userId: string, + @Body() dto: UpdateInvitationDto, + ) { + return this.facade.updateInvitation(slug, code, userId, dto); + } + + @Delete(':code') + @DeleteTeamInvitationSwagger() + async decline( + @Param('slug') slug: string, + @Param('code') code: string, + @GetUserId() userId: string, + ) { + return this.facade.declineInvitation(slug, code, userId); + } +} diff --git a/src/modules/teams/controller/me.controller.ts b/src/modules/teams/controller/me.controller.ts new file mode 100644 index 0000000..9ec2f60 --- /dev/null +++ b/src/modules/teams/controller/me.controller.ts @@ -0,0 +1,23 @@ +import { ApiBaseController, GetUser, GetUserId } from '@shared/decorators'; +import { MeService } from '../services'; +import { Get, Query } from '@nestjs/common'; +import { FindInvitesSwagger, FindTeamsSwagger } from './teams.swagger'; +import type { JwtPayload } from '@shared/types'; + +@ApiBaseController('users/me', 'Account Teams', true) +export class MeController { + constructor(private readonly facade: MeService) {} + + @Get('teams') + @FindTeamsSwagger() + // TODO: ADD TO QUERY DTO + async findMyTeams(@GetUserId() userId: string, @Query() query: any) { + return this.facade.getAll(userId, query); + } + + @Get('invites') + @FindInvitesSwagger() + async findMyInvites(@GetUser() user: JwtPayload) { + return this.facade.getMyInvites(user.email); + } +} diff --git a/src/modules/teams/controller/members.controller.ts b/src/modules/teams/controller/members.controller.ts new file mode 100644 index 0000000..1f908ad --- /dev/null +++ b/src/modules/teams/controller/members.controller.ts @@ -0,0 +1,37 @@ +import { Body, Delete, Get, Param, Patch } from '@nestjs/common'; +import { ApiBaseController, GetUserId } from '@shared/decorators'; +import { TeamMembersService } from '../services'; +import { GetMembersSwagger, RemoveMemberSwagger, UpdateMemberSwagger } from './teams.swagger'; +import type { UpdateMemberDto } from '../dtos/member.dto'; + +@ApiBaseController('teams/:slug', 'Teams Members', true) +export class TeamsMembersController { + constructor(private readonly facade: TeamMembersService) {} + + @Get('members') + @GetMembersSwagger() + async getMembers(@Param('slug') slug: string) { + return this.facade.getMembers(slug); + } + + @Patch('members/:userId') + @UpdateMemberSwagger() + async updateMember( + @Param('slug') slug: string, + @Param('userId') targetUserId: string, + @GetUserId() currentUserId: string, + @Body() dto: UpdateMemberDto, + ) { + return this.facade.updateMember(slug, currentUserId, targetUserId, dto); + } + + @Delete('members/:userId') + @RemoveMemberSwagger() + async removeMember( + @Param('slug') slug: string, + @Param('userId') targerUserId: string, + @GetUserId() currentUserId: string, + ) { + return this.facade.removeMember(slug, currentUserId, targerUserId); + } +} diff --git a/src/modules/teams/controller/settings.controller.ts b/src/modules/teams/controller/settings.controller.ts new file mode 100644 index 0000000..91484ab --- /dev/null +++ b/src/modules/teams/controller/settings.controller.ts @@ -0,0 +1,39 @@ +import { Body, Param, Patch, Put } from '@nestjs/common'; +import { ApiBaseController, ExtractFastifyFile } from '@shared/decorators'; +import { TeamsSettingsService } from '../services'; +import { + SyncTeamTagsSwagger, + PatchTeamAvatarSwagger, + PatchTeamBannerSwagger, +} from './teams.swagger'; +import type { FileUploadDto } from '../../media'; +import type { SyncTagsDto } from '../dtos'; + +@ApiBaseController('teams/:slug', 'Teams Settings', true) +export class TeamsSettingsController { + constructor(private readonly facade: TeamsSettingsService) {} + + @Put('tags') + @SyncTeamTagsSwagger() + async syncTags(@Param('slug') slug: string, @Body() dto: SyncTagsDto) { + return this.facade.syncTags(slug, dto.tags); + } + + @Patch('avatar') + @PatchTeamAvatarSwagger() + async updateTeamAvatar( + @ExtractFastifyFile() fileDto: FileUploadDto, + @Param('slug') slug: string, + ) { + return this.facade.updateTeamAvatar(slug, fileDto); + } + + @Patch('banner') + @PatchTeamBannerSwagger() + async updateTeamBanner( + @ExtractFastifyFile() fileDto: FileUploadDto, + @Param('slug') slug: string, + ) { + return this.facade.updateTeamBanner(slug, fileDto); + } +} diff --git a/src/modules/teams/controller/teams.controller.ts b/src/modules/teams/controller/teams.controller.ts new file mode 100644 index 0000000..6d85b34 --- /dev/null +++ b/src/modules/teams/controller/teams.controller.ts @@ -0,0 +1,47 @@ +import { Body, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post } from '@nestjs/common'; +import { ApiBaseController, GetUserId } from '@shared/decorators'; +import { TeamsService } from '../services'; +import { + CreateTeamSwagger, + FindOneTeamSwagger, + RemoveTeamSwagger, + UpdateTeamSwagger, + CheckSlugSwagger, +} from './teams.swagger'; +import { CreateTeamDto } from '../dtos'; + +@ApiBaseController('teams', 'Teams', true) +export class TeamsController { + constructor(private readonly facade: TeamsService) {} + + @Post() + @CreateTeamSwagger() + async create(@GetUserId() userId: string, @Body() dto: CreateTeamDto) { + return this.facade.create(userId, dto); + } + + @Get('check-slug/:slug') + @CheckSlugSwagger() + async checkSlug(@Param('slug') slug: string) { + return this.facade.checkSlug(slug); + } + + @Get(':slug') + @FindOneTeamSwagger() + async findOne(@Param('slug') slug: string) { + return this.facade.getOne(slug); + } + + @Patch(':slug') + @UpdateTeamSwagger() + async update(@Param('slug') slug: string, @GetUserId() userId: string, @Body() dto: any) { + return this.facade.update(slug, userId, dto); + } + + @Delete(':slug') + @RemoveTeamSwagger() + @HttpCode(HttpStatus.OK) + async remove(@Param('slug') slug: string, @GetUserId() userId: string) { + return this.facade.remove(slug, userId); + } +} diff --git a/src/modules/teams/controller/teams.swagger.ts b/src/modules/teams/controller/teams.swagger.ts new file mode 100644 index 0000000..c770e62 --- /dev/null +++ b/src/modules/teams/controller/teams.swagger.ts @@ -0,0 +1,386 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiConsumes } from '@nestjs/swagger'; +import { ActionResponse } from '@shared/dtos'; +import { + ApiBadRequest, + ApiConflict, + ApiForbidden, + ApiNotFound, + ApiUnauthorized, + ApiValidationError, +} from '@shared/error'; +import { + CreateTeamDto, + InviteMemberDto, + TeamInvitationResponse, + SyncTagsDto, + UpdateTeamDto, + TagResponse, + TeamMemberResponse, + CheckSlugResponse, + UpdateMemberDto, + UpdateInvitationDto, + UserTeamResponse, + UserInviteResponse, +} from '../dtos'; +import { FileUploadResponse } from '../../media/dtos'; + +export const CreateTeamSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Создать новую команду' }), + ApiBody({ type: CreateTeamDto.Output }), + ApiResponse({ + status: 201, + description: 'Команда успешно создана', + type: ActionResponse.Output, + }), + ApiConflict('Команда с таким slug уже существует'), + ApiValidationError(), + ApiUnauthorized(), + ); + +export const CheckSlugSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Проверить доступность слага', + description: 'Проверяет, свободен ли уникальный адрес команды для использования.', + }), + ApiParam({ + name: 'slug', + description: 'Желаемый слаг команды', + example: 'my-super-team', + }), + ApiResponse({ + status: 200, + description: 'Результат проверки доступности', + type: CheckSlugResponse.Output, + }), + ApiUnauthorized(), + ); + +export const FindTeamsSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Получить список команд пользователя', + description: + 'Возвращает все команды, в которых текущий пользователь является участником или владельцем.', + }), + ApiResponse({ + status: 200, + description: 'Список команд получен', + type: [UserTeamResponse.Output], + }), + ApiUnauthorized(), + ); + +export const FindInvitesSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Получить список входящих приглашений', + description: + 'Возвращает все активные приглашения в команды, отправленные на email текущего пользователя.', + }), + ApiResponse({ + status: 200, + description: 'Список приглашений успешно получен', + type: [UserInviteResponse.Output], + }), + ApiUnauthorized(), + ); + +export const FindOneTeamSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Получить детальную информацию о команде по slug' }), + ApiParam({ name: 'slug', description: 'Уникальный идентификатор (слаг) команды' }), + ApiResponse({ + status: 200, + description: 'Данные команды получены', + type: Object, + }), + ApiNotFound('Команда не найдена'), + ApiUnauthorized(), + ); + +export const UpdateTeamSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Обновить данные команды' }), + ApiBody({ type: UpdateTeamDto.Output }), + ApiParam({ name: 'slug', description: 'Слаг команды для редактирования' }), + ApiResponse({ + status: 200, + description: 'Команда успешно обновлена', + type: ActionResponse.Output, + }), + ApiForbidden(), + ApiNotFound(), + ApiValidationError(), + ApiUnauthorized(), + ); + +export const RemoveTeamSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Удалить команду' }), + ApiParam({ name: 'slug', description: 'Слаг команды для удаления' }), + ApiResponse({ + status: 200, + description: 'Команда успешно удалена', + type: ActionResponse.Output, + }), + ApiForbidden(), + ApiNotFound(), + ApiUnauthorized(), + ); + +export const SyncTeamTagsSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Синхронизировать теги команды' }), + ApiBody({ type: SyncTagsDto.Output }), + ApiResponse({ status: 200, description: 'Теги обновлены', type: ActionResponse.Output }), + ApiForbidden(), + ApiNotFound(), + ApiUnauthorized(), + ); + +export const GetAllTagsSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Получить список всех тегов с пагинацией', + description: + 'Возвращает список всех тегов в системе с пагинацией. Используется для поиска и автокомплита при создании/редактировании команд.', + }), + ApiResponse({ + status: 200, + description: 'Список тегов успешно получен', + type: TagResponse.Output, + }), + ApiUnauthorized(), + ); + +export const GetMembersSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Получить список всех участников команды' }), + ApiParam({ name: 'slug', description: 'Слаг команды' }), + ApiResponse({ + status: 200, + description: 'Список участников получен', + type: [TeamMemberResponse.Output], + }), + ApiUnauthorized(), + ApiForbidden(), + ); + +export const InviteMemberSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Пригласить пользователя в команду по Email', + description: + 'Создает запись об участнике со статусом "pending".' + + ' Если пользователь уже зарегистрирован — он увидит приглашение в разделе "my/invites".' + + ' Если нет — ему уйдет письмо на указанный Email.', + }), + ApiBody({ type: InviteMemberDto.Output }), + ApiParam({ name: 'slug', description: 'Слаг команды, в которую приглашаем' }), + ApiResponse({ + status: 201, + description: 'Инвайт создан и отправлен', + type: ActionResponse.Output, + }), + ApiValidationError('Некорректный формат Email или роль не поддерживается'), + ApiUnauthorized(), + ApiForbidden(), + ); + +export const UpdateMemberSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Изменить роль или статус участника', + description: + 'Позволяет изменить роль участника (member -> admin) или вручную изменить его статус.' + + ' Владелец команды (Owner) не может понизить свою роль через этот эндпоинт.', + }), + ApiBody({ type: UpdateMemberDto.Output }), + ApiParam({ name: 'slug', description: 'Слаг команды' }), + ApiParam({ name: 'userId', description: 'ID пользователя, чьи права редактируются' }), + ApiResponse({ + status: 200, + description: 'Данные участника обновлены', + type: ActionResponse.Output, + }), + ApiNotFound('Участник или команда не найдены'), + ApiUnauthorized(), + ApiForbidden(), + ); + +export const RemoveMemberSwagger = () => + applyDecorators( + ApiOperation({ summary: 'Удалить участника из команды' }), + ApiParam({ name: 'slug', description: 'Слаг команды' }), + ApiParam({ name: 'userId', description: 'ID пользователя' }), + ApiResponse({ + status: 200, + type: ActionResponse.Output, + description: 'Участник успешно удален', + }), + ApiNotFound(), + ApiUnauthorized(), + ApiForbidden(), + ); + +export const PatchTeamAvatarSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Обновить аватар команды', + description: 'Загрузка файла изображения для профиля команды.', + }), + ApiConsumes('multipart/form-data'), + ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }), + ApiResponse({ + status: 200, + description: 'Аватар команды успешно обновлен.', + type: FileUploadResponse.Output, + }), + ApiBadRequest('Файл не передан или имеет неверный формат'), + ApiNotFound('Команда не найдена'), + ApiUnauthorized(), + ApiForbidden(), + ); + +export const PatchTeamBannerSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Обновить баннер команды', + description: 'Загрузка файла изображения для обложки (баннера) команды.', + }), + ApiConsumes('multipart/form-data'), + ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }), + ApiResponse({ + status: 200, + description: 'Баннер команды успешно обновлен.', + type: FileUploadResponse.Output, + }), + ApiBadRequest('Файл не передан или имеет неверный формат'), + ApiNotFound('Команда не найдена'), + ApiUnauthorized(), + ApiForbidden(), + ); + +export const AcceptInviteSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Принять приглашение в команду', + description: + 'Активирует участие пользователя в команде по уникальному коду приглашения.' + + ' После успешного принятия статус участника меняется с "pending" на "active".' + + ' Система автоматически связывает текущего авторизованного пользователя с инвайтом через Email.', + }), + ApiParam({ + name: 'code', + description: 'Уникальный код/токен приглашения (из ссылки или письма)', + example: '7df1-4a2b-9e8c', + }), + ApiResponse({ + status: 200, + description: 'Приглашение успешно принято. Пользователь теперь участник команды.', + type: ActionResponse.Output, + }), + ApiBadRequest('Невалидный код, срок действия приглашения истек или оно уже использовано'), + ApiNotFound('Приглашение с таким кодом не найдено'), + ApiConflict('Пользователь уже является участником этой команды'), + ApiUnauthorized(), + ); + +export const GetTeamInvitationsSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Получить список всех приглашений в команду', + description: 'Возвращает все активные инвайты команды. Доступно только owner/admin.', + }), + ApiParam({ name: 'slug', description: 'Слаг команды' }), + ApiResponse({ + status: 200, + description: 'Список приглашений команды', + type: [TeamInvitationResponse.Output], + }), + ApiNotFound('Команда не найдена'), + ApiForbidden('Недостаточно прав (только owner/admin)'), + ApiUnauthorized(), + ); + +export const GetTeamInvitationSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Получить приглашение по коду', + description: + 'Возвращает данные инвайта по коду в рамках команды. Доступно только owner/admin.', + }), + ApiParam({ name: 'slug', description: 'Слаг команды' }), + ApiParam({ name: 'code', description: 'Код инвайта' }), + ApiResponse({ + status: 200, + description: 'Инвайт найден', + type: TeamInvitationResponse.Output, + }), + ApiNotFound('Инвайт или команда не найдены'), + ApiForbidden('Недостаточно прав (только owner/admin)'), + ApiUnauthorized(), + ); + +export const UpdateTeamInvitationSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Обновить приглашение (только роль)', + description: + 'Позволяет изменить только поле role у существующего инвайта. TTL сохраняется.', + }), + ApiParam({ name: 'slug', description: 'Слаг команды' }), + ApiParam({ name: 'code', description: 'Код инвайта' }), + ApiBody({ type: UpdateInvitationDto.Output }), + ApiResponse({ + status: 200, + description: 'Инвайт обновлён', + type: TeamInvitationResponse.Output, + }), + ApiValidationError(), + ApiNotFound('Инвайт или команда не найдены'), + ApiForbidden('Недостаточно прав (только owner/admin)'), + ApiUnauthorized(), + ); + +export const DeleteTeamInvitationSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Удалить приглашение', + description: + 'Удаляет инвайт и чистит индексы в Redis (team:invites и user:invites). Доступно только owner/admin.', + }), + ApiParam({ name: 'slug', description: 'Слаг команды' }), + ApiParam({ name: 'code', description: 'Код инвайта' }), + ApiResponse({ + status: 200, + description: 'Инвайт удалён', + type: ActionResponse.Output, + }), + ApiNotFound('Инвайт или команда не найдены'), + ApiForbidden('Недостаточно прав (только owner/admin)'), + ApiUnauthorized(), + ); diff --git a/src/modules/teams/dtos/index.ts b/src/modules/teams/dtos/index.ts new file mode 100644 index 0000000..f87edb5 --- /dev/null +++ b/src/modules/teams/dtos/index.ts @@ -0,0 +1,16 @@ +export { + InviteMemberDto, + UpdateMemberDto, + TeamMemberResponse, + UserInviteResponse, +} from './member.dto'; +export { UpdateInvitationDto, TeamInvitationResponse } from './invitation.dto'; +export { + CreateTeamDto, + UpdateTeamDto, + FindTagsQuery, + SyncTagsDto, + UserTeamResponse, + TagResponse, + CheckSlugResponse, +} from './team.dto'; diff --git a/src/modules/teams/dtos/invitation.dto.ts b/src/modules/teams/dtos/invitation.dto.ts new file mode 100644 index 0000000..d3908a9 --- /dev/null +++ b/src/modules/teams/dtos/invitation.dto.ts @@ -0,0 +1,38 @@ +import { z } from 'zod/v4'; +import { createZodDto } from 'nestjs-zod'; +import { roleEnum, TeamRole } from '../entities/enums'; + +export const UpdateInvitationSchema = z.object({ + role: z + .enum(roleEnum.enumValues) + .describe('Новая роль, которая будет назначена пользователю после принятия инвайта'), +}); + +export class UpdateInvitationDto extends createZodDto(UpdateInvitationSchema) {} + +export const TeamInvitationSchema = z.object({ + code: z.string().describe('Код инвайта'), + teamId: z.string().describe('ID команды'), + teamName: z.string().describe('Название команды'), + teamAvatar: z.string().nullable().describe('Аватар команды'), + email: z.string().email().describe('Email приглашённого пользователя'), + role: z.string().describe('Роль, которая будет назначена после принятия инвайта'), + inviterId: z.string().describe('ID пользователя, отправившего приглашение'), + inviterName: z.string().describe('Имя пригласившего'), + createdAt: z.string().datetime().describe('Дата создания инвайта (ISO 8601)'), + expiresAt: z.string().datetime().describe('Дата истечения инвайта (ISO 8601)'), +}); + +export class TeamInvitationResponse extends createZodDto(TeamInvitationSchema) {} + +export interface TeamInvite { + teamId: string; + teamName: string; + teamAvatar: string | null; + email: string; + role: TeamRole; + inviterId: string; + inviterName: string; + createdAt: string; + expiresAt: string; +} diff --git a/src/modules/teams/dtos/member.dto.ts b/src/modules/teams/dtos/member.dto.ts new file mode 100644 index 0000000..ac48ccd --- /dev/null +++ b/src/modules/teams/dtos/member.dto.ts @@ -0,0 +1,62 @@ +import { z } from 'zod/v4'; +import { createZodDto } from 'nestjs-zod'; +import { roleEnum } from '../entities'; + +export const InviteMemberSchema = z.object({ + email: z.string().email().describe('Email пользователя, которого нужно пригласить'), + role: z + .enum(roleEnum.enumValues) + .default('member') + .describe('Роль, которая будет назначена пользователю после принятия инвайта'), +}); + +export class InviteMemberDto extends createZodDto(InviteMemberSchema) {} + +const UpdateMemberDtoSchema = z + .object({ + role: z.string().optional().describe('Новая роль участника'), + status: z.string().optional().describe('Новый статус (active, blocked и т.д.)'), + }) + .refine((data) => Object.keys(data).length > 0, { + error: 'Необходимо передать хотя бы одно поле для обновления', + abort: true, + }); + +export class UpdateMemberDto extends createZodDto(UpdateMemberDtoSchema) {} + +export const TeamMemberResponseSchema = z.object({ + id: z.string().describe('Уникальный ID пользователя (UUID или ULID)'), + role: z + .enum(['owner', 'admin', 'member']) + .describe('Роль участника в рамках конкретной команды'), + status: z + .enum(['active', 'pending', 'blocked']) + .describe('Текущий статус членства (активен, ожидает приглашения, заблокирован)'), + fullName: z.string().describe('Полное имя для отображения (Фамилия Имя Отчество)'), + firstName: z.string().describe('Имя пользователя'), + lastName: z.string().describe('Фамилия пользователя'), + avatarUrl: z + .string() + .url() + .nullable() + .describe('Прямая ссылка на изображение профиля или null, если не задано'), + + initials: z.string().max(2).describe('Две буквы для аватара-заглушки (например, "ИИ")'), + joinedAt: z + .string() + .datetime() + .describe('Дата и время вступления в команду в формате ISO 8601'), +}); + +export class TeamMemberResponse extends createZodDto(TeamMemberResponseSchema) {} + +export const UserInviteSchema = z.object({ + code: z.string().describe('Код инвайта'), + teamName: z.string().describe('Название команды'), + teamAvatar: z.string().nullable().describe('Аватар команды'), + role: z.string().describe('Роль'), + inviterName: z.string().describe('Имя пригласившего'), + expiresAt: z.string().datetime().describe('Дата истечения'), +}); + +export class UserInviteResponse extends createZodDto(UserInviteSchema) {} diff --git a/src/modules/teams/dtos/team.dto.ts b/src/modules/teams/dtos/team.dto.ts new file mode 100644 index 0000000..4fe2fb8 --- /dev/null +++ b/src/modules/teams/dtos/team.dto.ts @@ -0,0 +1,99 @@ +import { z } from 'zod/v4'; +import { createZodDto } from 'nestjs-zod'; +import { createPaginationSchema } from '../../../shared/schemas'; + +export const CreateTeamSchema = z.object({ + name: z.string().min(2).max(100).describe('Название команды, отображаемое в интерфейсе'), + description: z + .string() + .min(10) + .max(500) + .describe('Краткое описание деятельности или целей команды'), + slug: z.string().optional().describe('Уникальная ссылка на изображение команду'), + tags: z + .array(z.string()) + .optional() + .superRefine((items, ctx) => { + if (!items) return; + const lowerItems = items.map((i) => i.toLowerCase()); + const hasDuplicates = new Set(lowerItems).size !== items.length; + + if (hasDuplicates) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Теги в списке не должны повторяться (регистр не важен)', + }); + } + }) + .describe('Список строковых названий тегов для классификации'), +}); + +export class CreateTeamDto extends createZodDto(CreateTeamSchema) {} +export class UpdateTeamDto extends createZodDto( + CreateTeamSchema.partial().refine((data) => Object.keys(data).length > 0, { + error: 'Необходимо передать хотя бы одно поле для обновления', + abort: true, + }), +) {} + +export const TagSchema = z.object({ + id: z.string().describe('Уникальный идентификатор тега (CUID2)'), + name: z.string().min(1).max(50).describe('Название тега (например, "Backend", "Design")'), +}); + +export const SyncTagsSchema = z.object({ + tags: z + .array(z.string()) + .min(1, 'Список тегов не может быть пустым') + .max(15, 'Нельзя добавить более 15 тегов за раз') + .describe( + 'Массив названий тегов для привязки к команде. Если тега нет в базе, он будет создан.', + ), +}); + +const FindTagsQuerySchema = z.object({ + search: z.string().optional().describe('Поисковый запрос для фильтрации тегов по названию'), + page: z.coerce.number().int().min(1).default(1).describe('Номер страницы (от 1)'), + limit: z.coerce + .number() + .int() + .min(1) + .max(100) + .default(20) + .describe('Количество возвращаемых результатов (1-100)'), +}); + +export class TagResponse extends createZodDto(createPaginationSchema(TagSchema)) {} +export class SyncTagsDto extends createZodDto(SyncTagsSchema) {} +export class FindTagsQuery extends createZodDto(FindTagsQuerySchema) {} + +export const CheckSlugResponseSchema = z.object({ + available: z + .boolean() + .describe('Флаг доступности: true — адрес свободен, false — уже занят или некорректен'), +}); + +export class CheckSlugResponse extends createZodDto(CheckSlugResponseSchema) {} + +export const TeamPermissionsSchema = z.object({ + canEdit: z.boolean().describe('Разрешено ли редактировать настройки и профиль команды'), + canDelete: z + .boolean() + .describe('Разрешено ли полностью удалить команду (только для владельца)'), + canManageMembers: z.boolean().describe('Разрешено ли менять роли и исключать участников'), + canInvite: z.boolean().describe('Разрешено ли приглашать новых участников'), + isOwner: z.boolean().describe('Является ли текущий пользователь владельцем (Owner)'), +}); + +export const UserTeamSchema = z.object({ + id: z.string().uuid().describe('Уникальный ID команды'), + name: z.string().describe('Название команды'), + slug: z.string().describe('Уникальный URL-путь команды'), + description: z.string().nullable().describe('Краткое описание команды'), + avatarUrl: z.string().nullable().describe('URL изображения профиля команды'), + role: z.string().describe('Системное название роли пользователя'), + joinedAt: z.string().datetime().describe('Дата, когда пользователь вступил в команду'), + permissions: TeamPermissionsSchema.describe('Объект прав доступа текущего пользователя'), +}); + +export class UserTeamResponse extends createZodDto(UserTeamSchema) {} diff --git a/src/modules/teams/entities/enums.ts b/src/modules/teams/entities/enums.ts new file mode 100644 index 0000000..2dba2b2 --- /dev/null +++ b/src/modules/teams/entities/enums.ts @@ -0,0 +1,17 @@ +import { baseSchema } from '@shared/entities'; + +export const roleEnum = baseSchema.enum('team_role', [ + 'owner', + 'admin', // управление юзерами, настройками + 'lead', // управление проектами + 'moderator', // чистка контента/сообщений + 'member', // обычный работяга + 'viewer', // просто смотрит +]); +export type TeamRole = (typeof roleEnum.enumValues)[number]; + +export const statusEnum = baseSchema.enum('member_status', [ + 'active', // Полноценный участник + 'banned', // Заблокирован не может вернуться по инвайту + 'inactive', // Доступ закрыт, но запись сохранена +]); diff --git a/src/modules/teams/entities/index.ts b/src/modules/teams/entities/index.ts new file mode 100644 index 0000000..f996b3f --- /dev/null +++ b/src/modules/teams/entities/index.ts @@ -0,0 +1,3 @@ +export { tags, teamsToTags, teams, teamMembers } from './teams.entity'; +export { roleEnum, statusEnum } from './enums'; +export * from './teams.domain'; diff --git a/src/modules/teams/entities/teams.domain.ts b/src/modules/teams/entities/teams.domain.ts new file mode 100644 index 0000000..75c044b --- /dev/null +++ b/src/modules/teams/entities/teams.domain.ts @@ -0,0 +1,22 @@ +import type { InferSelectModel, InferInsertModel } from 'drizzle-orm'; +import { teams, teamMembers, tags, teamsToTags } from './teams.entity'; + +export type Team = InferSelectModel; +export type NewTeam = InferInsertModel; + +export type TeamMember = InferSelectModel; +export type NewTeamMember = InferInsertModel; + +export type Tag = InferSelectModel; +export type NewTag = InferInsertModel; + +export type TeamToTag = InferSelectModel; +export type NewTeamToTag = InferInsertModel; + +export type TeamWithMembers = Team & { + members: TeamMember[]; +}; + +export type TeamWithTags = Team & { + tags: Tag[]; +}; diff --git a/src/modules/teams/entities/teams.entity.ts b/src/modules/teams/entities/teams.entity.ts new file mode 100644 index 0000000..44603e0 --- /dev/null +++ b/src/modules/teams/entities/teams.entity.ts @@ -0,0 +1,74 @@ +import { primaryKey, timestamp, text, varchar, index } from 'drizzle-orm/pg-core'; +import { createId } from '@paralleldrive/cuid2'; +import { roleEnum, statusEnum } from './enums'; +import { baseSchema, users } from '@shared/entities'; +import { uniqueIndex } from 'drizzle-orm/pg-core'; +import { isNull } from 'drizzle-orm'; + +export const teams = baseSchema.table( + 'teams', + { + id: text('id') + .primaryKey() + .$defaultFn(() => createId()), + slug: varchar('slug', { length: 120 }).unique().notNull(), + name: varchar('name', { length: 100 }).notNull(), + description: text('description'), + avatarUrl: text('avatar_url'), + coverUrl: text('cover_url'), + ownerId: text('owner_id').references(() => users.id, { onDelete: 'set null' }), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), + deletedAt: timestamp('deleted_at'), + }, + (t) => ({ + uniqueActiveSlug: uniqueIndex('team_active_slug_idx').on(t.slug).where(isNull(t.deletedAt)), + slugIdx: index('team_slug_idx').on(t.slug), + ownerIdx: index('team_owner_idx').on(t.ownerId), + softDeleteIdx: index('team_deleted_at_idx').on(t.deletedAt), + }), +); + +export const teamMembers = baseSchema.table( + 'team_members', + { + teamId: text('team_id') + .references(() => teams.id, { onDelete: 'cascade' }) + .notNull(), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), + role: roleEnum('role').default('member').notNull(), + status: statusEnum('status').default('inactive').notNull(), + joinedAt: timestamp('joined_at'), + createdAt: timestamp('created_at').defaultNow().notNull(), + }, + (t) => ({ + pk: primaryKey({ columns: [t.teamId, t.userId] }), + statusIdx: index('member_status_idx').on(t.status), + userRoleIdx: index('member_role_idx').on(t.userId, t.role), + }), +); + +export const tags = baseSchema.table('tags', { + id: text('id') + .primaryKey() + .$defaultFn(() => createId()), + name: varchar('name', { length: 50 }).unique().notNull(), +}); + +export const teamsToTags = baseSchema.table( + 'teams_to_tags', + { + teamId: text('team_id') + .references(() => teams.id, { onDelete: 'cascade' }) + .notNull(), + tagId: text('tag_id') + .references(() => tags.id, { onDelete: 'cascade' }) + .notNull(), + }, + (t) => ({ + pk: primaryKey({ columns: [t.teamId, t.tagId] }), + tagIdx: index('teams_to_tags_tag_id_idx').on(t.tagId), + }), +); diff --git a/src/modules/teams/index.ts b/src/modules/teams/index.ts new file mode 100644 index 0000000..7f616ae --- /dev/null +++ b/src/modules/teams/index.ts @@ -0,0 +1,2 @@ +export { TeamsModule } from './teams.module'; +export { FindTeamCommand, FindTeamMemberCommand } from './commands'; diff --git a/src/modules/teams/mappers/index.ts b/src/modules/teams/mappers/index.ts new file mode 100644 index 0000000..f09718a --- /dev/null +++ b/src/modules/teams/mappers/index.ts @@ -0,0 +1 @@ +export { TeamMemberMapper } from './member.mapper'; diff --git a/src/modules/teams/mappers/member.mapper.ts b/src/modules/teams/mappers/member.mapper.ts new file mode 100644 index 0000000..cf2f6f5 --- /dev/null +++ b/src/modules/teams/mappers/member.mapper.ts @@ -0,0 +1,69 @@ +import type { RawMemberRow, RawMemberTeams } from '../repository'; + +export class TeamMemberMapper { + public static toDetail(row: RawMemberRow) { + const { firstName, lastName, middleName, avatarUrl, userId, ...rest } = row; + + const fullName = + [lastName, firstName, middleName].filter(Boolean).join(' ') || 'Unknown User'; + + return { + id: userId, + ...rest, + firstName, + lastName, + middleName, + fullName, + avatarUrl, + initials: this.getInitials(firstName, lastName), + }; + } + + public static toList(rows: RawMemberRow[]) { + return rows.map((row) => this.toDetail(row)); + } + + public static toUserTeam(row: RawMemberTeams) { + const role = row.role; + + return { + id: row.id, + name: row.name, + slug: row.slug, + description: row.description, + avatarUrl: row.avatarUrl, + role: role, + joinedAt: row.joinedAt, + permissions: { + canEdit: ['owner', 'admin'].includes(role), + canDelete: role === 'owner', + canManageMembers: ['owner', 'admin'].includes(role), + canInvite: ['owner', 'admin'].includes(role), + isOwner: role === 'owner', + }, + }; + } + + public static toPublicInvite(raw: string | null, code: string) { + if (!raw) return null; + try { + const p = JSON.parse(raw); + return { + code, + teamName: p.teamName, + teamAvatar: p.teamAvatar ?? null, + inviterName: p.inviterName, + role: p.role, + expiresAt: p.expiresAt, + }; + } catch { + return null; + } + } + + private static getInitials(fName: string | null, lName: string | null): string { + const first = fName?.[0] ?? ''; + const last = lName?.[0] ?? ''; + return (first + last).toUpperCase() || '?'; + } +} diff --git a/src/modules/teams/repository/index.ts b/src/modules/teams/repository/index.ts new file mode 100644 index 0000000..f78a0c8 --- /dev/null +++ b/src/modules/teams/repository/index.ts @@ -0,0 +1,6 @@ +export { TeamsRepository } from './teams.repository'; +export { + ITeamsRepository, + type RawMemberRow, + type RawMemberTeams, +} from './teams.repository.interface'; diff --git a/src/modules/teams/repository/teams.repository.interface.ts b/src/modules/teams/repository/teams.repository.interface.ts new file mode 100644 index 0000000..f02a9c9 --- /dev/null +++ b/src/modules/teams/repository/teams.repository.interface.ts @@ -0,0 +1,60 @@ +import type { Team, NewTeam, NewTeamMember, Tag } from '../entities'; + +type TResponse = { success: boolean; tags: number; teamId: string }; + +export type RawMemberRow = { + userId: string; + role: string; + status: string; + joinedAt: Date | string | null; + firstName: string | null; + lastName: string | null; + middleName: string | null; + avatarUrl: string | null; + email?: string; +}; + +export type RawMemberTeams = { + id: string; + name: string; + slug: string; + description: string | null; + avatarUrl: string | null; + role: string; + joinedAt: Date; +}; + +export interface ITeamsRepository { + create(ownerId: string, dto: NewTeam, tags?: string[]): Promise; + update(id: string, dto: Partial, tags?: string[]): Promise; + remove(id: string, userId: string): Promise; + + isSlugAvailable(slug: string): Promise; + + findMember(teamId: string, userId: string): Promise; + findMembers(teamId: string): Promise; + findBySlug(slug: string): Promise; + findByUser( + userId: string, + // TODO: ADD ZOD QUERY + pagination: { search?: string; limit?: number; offset?: number }, + ): Promise; + + findAllTags(options: { + search?: string; + limit?: number; + offset?: number; + }): Promise<{ data: Tag[]; total: number }>; + syncTags(teamId: string, tagNames: string[]): Promise; + + updateTeamAvatar(teamId: string, url: string): Promise; + updateTeamBanner(teamId: string, url: string): Promise; + + addMember(dto: NewTeamMember): Promise; + updateMember( + teamId: string, + userId: string, + dto: { role?: string; status?: string }, + ): Promise; + removeMember(teamId: string, userId: string): Promise; +} diff --git a/src/modules/teams/repository/teams.repository.ts b/src/modules/teams/repository/teams.repository.ts new file mode 100644 index 0000000..59f078b --- /dev/null +++ b/src/modules/teams/repository/teams.repository.ts @@ -0,0 +1,292 @@ +import { Inject, Logger } from '@nestjs/common'; +import { ITeamsRepository } from './teams.repository.interface'; +import { DATABASE_SERVICE, DatabaseService } from '@libs/database'; +import * as schema from '../entities'; +import * as scUsers from '@core/user/infrastructure/persistence/models'; +import { and, asc, count, desc, eq, ilike, inArray, isNull, sql } from 'drizzle-orm'; + +export class TeamsRepository implements ITeamsRepository { + private logger = new Logger(TeamsRepository.name); + + constructor( + @Inject(DATABASE_SERVICE) + private readonly db: DatabaseService, + ) {} + + public isSlugAvailable = async (slug: string) => { + const result = await this.db + .select({ id: schema.teams.id }) + .from(schema.teams) + .where(eq(schema.teams.slug, slug)); + + return result.length === 0; + }; + + public addMember = async (dto: schema.NewTeamMember) => { + const { rowCount } = await this.db + .insert(schema.teamMembers) + .values(dto) + .onConflictDoNothing({ + target: [schema.teamMembers.teamId, schema.teamMembers.userId], + }); + + return (rowCount ?? 0) > 0; + }; + + public create = async (ownerId: string, dto: schema.NewTeam, tags?: string[]) => { + return this.db.transaction(async (tx) => { + const [{ teamId }] = await tx + .insert(schema.teams) + .values({ ...dto, ownerId }) + .returning({ teamId: schema.teams.id }); + + let insertedTagsCount = 0; + + if (tags?.length) { + const insertedTags = await tx + .insert(schema.tags) + .values(tags.map((name) => ({ name }))) + .onConflictDoUpdate({ + target: schema.tags.name, + set: { name: sql`${schema.tags.name}` }, + }) + .returning({ id: schema.tags.id }); + + if (insertedTags.length > 0) { + await tx.insert(schema.teamsToTags).values( + insertedTags.map((tag) => ({ + teamId, + tagId: tag.id, + })), + ); + + insertedTagsCount = insertedTags.length; + } + } + + await tx.insert(schema.teamMembers).values({ + teamId, + userId: ownerId, + role: 'owner', + status: 'active', + joinedAt: new Date(), + }); + + return { + success: true, + teamId, + tags: insertedTagsCount, + }; + }); + }; + + public update = async (id: string, dto: Partial, tags?: string[]) => { + return this.db.transaction(async (tx) => { + const [{ teamId }] = await tx + .update(schema.teams) + .set(dto) + .where(eq(schema.teams.id, id)) + .returning({ teamId: schema.teams.id }); + + if (tags?.length) { + } + + return { + success: true, + teamId, + tags: 0, + }; + }); + }; + + public remove = async (teamId: string, userId) => { + const suffix = Date.now().toString(); + + const { rowCount } = await this.db + .update(schema.teams) + .set({ + deletedAt: new Date(), + slug: sql`${schema.teams.slug} || '-' || ${suffix}`, + }) + .where(and(eq(schema.teams.id, teamId), eq(schema.teams.ownerId, userId))); + + return (rowCount ?? 0) > 0; + }; + + public findMember = async (teamId: string, userId: string) => { + const [member] = await this.membersQuery.where( + and(eq(schema.teamMembers.teamId, teamId), eq(schema.teamMembers.userId, userId)), + ); + + return member || null; + }; + + public findMembers = async (teamId: string) => { + return this.membersQuery + .where(eq(schema.teamMembers.teamId, teamId)) + .orderBy(desc(schema.teamMembers.joinedAt)); + }; + + public findByUser = async ( + userId: string, + pagination: { search?: string; limit?: number; offset?: number }, + ) => { + const { search, limit = 10, offset = 0 } = pagination; + + const filters = [ + eq(schema.teamMembers.userId, userId), + eq(schema.teamMembers.status, 'active'), + isNull(schema.teams.deletedAt), + ]; + + if (search) { + filters.push(ilike(schema.teams.name, `%${search}%`)); + } + + const query = this.db + .select({ + id: schema.teams.id, + name: schema.teams.name, + slug: schema.teams.slug, + description: schema.teams.description, + avatarUrl: schema.teams.avatarUrl, + role: schema.teamMembers.role, + joinedAt: schema.teamMembers.joinedAt, + }) + .from(schema.teamMembers) + .innerJoin(schema.teams, eq(schema.teams.id, schema.teamMembers.teamId)) + .where(and(...filters)) + .orderBy(desc(schema.teamMembers.joinedAt)) + .limit(limit) + .offset(offset); + + return query; + }; + + public findAllTags = async (options: { search?: string; limit?: number; offset?: number }) => { + const cleanSearch = options.search?.trim(); + const escapedSearch = cleanSearch?.replace(/([%_\\])/g, '\\$1'); + + const whereCondition = escapedSearch + ? ilike(schema.tags.name, `%${escapedSearch}%`) + : undefined; + + const [data, [{ total }]] = await Promise.all([ + this.db + .select() + .from(schema.tags) + .where(whereCondition) + .limit(options.limit) + .offset(options.offset) + .orderBy(asc(schema.tags.name)), + + this.db.select({ total: count() }).from(schema.tags).where(whereCondition), + ]); + + return { + data, + total: Number(total ?? 0), + }; + }; + + public findBySlug = async (slug: string) => { + const [team] = await this.db.select().from(schema.teams).where(eq(schema.teams.slug, slug)); + if (!team) return null; + return team; + }; + + public removeMember = async (teamId: string, userId: string) => { + const result = await this.db + .delete(schema.teamMembers) + .where( + and(eq(schema.teamMembers.teamId, teamId), eq(schema.teamMembers.userId, userId)), + ); + + return (result.rowCount ?? 0) > 0; + }; + + public syncTags = async (teamId: string, tagNames: string[]) => { + await this.db.transaction(async (tx) => { + await tx.delete(schema.teamsToTags).where(eq(schema.teamsToTags.teamId, teamId)); + + if (tagNames.length === 0) { + return; + } + + await tx + .insert(schema.tags) + .values(tagNames.map((name) => ({ name }))) + .onConflictDoNothing({ target: schema.tags.name }); + + const existingTags = await tx + .select({ id: schema.tags.id }) + .from(schema.tags) + .where(inArray(schema.tags.name, tagNames)); + + await tx + .insert(schema.teamsToTags) + .values(existingTags.map((tag) => ({ teamId, tagId: tag.id }))); + }); + + return true; + }; + + public updateMember = async ( + teamId: string, + userId: string, + dto: Partial, + ) => { + const { role, status } = dto; + + const data = { + role, + ...(status === 'active' ? { joinedAt: new Date() } : {}), + }; + + const result = await this.db + .update(schema.teamMembers) + .set(data) + .where( + and(eq(schema.teamMembers.teamId, teamId), eq(schema.teamMembers.userId, userId)), + ); + + return (result.rowCount ?? 0) > 0; + }; + + public async updateTeamAvatar(teamId: string, url: string): Promise { + const { rowCount } = await this.db + .update(schema.teams) + .set({ avatarUrl: url, updatedAt: new Date() }) + .where(eq(schema.teams.id, teamId)); + return (rowCount ?? 0) > 0; + } + + public async updateTeamBanner(teamId: string, url: string): Promise { + const { rowCount } = await this.db + .update(schema.teams) + .set({ coverUrl: url, updatedAt: new Date() }) + .where(eq(schema.teams.id, teamId)); + return (rowCount ?? 0) > 0; + } + + private get memberSelection() { + return { + userId: schema.teamMembers.userId, + role: schema.teamMembers.role, + status: schema.teamMembers.status, + joinedAt: schema.teamMembers.joinedAt, + firstName: scUsers.users.firstName, + lastName: scUsers.users.lastName, + middleName: scUsers.users.middleName, + avatarUrl: scUsers.users.avatarUrl, + email: scUsers.users.email, + }; + } + + private get membersQuery() { + return this.db + .select(this.memberSelection) + .from(schema.teamMembers) + .innerJoin(scUsers.users, eq(schema.teamMembers.userId, scUsers.users.id)); + } +} diff --git a/src/modules/teams/services/index.ts b/src/modules/teams/services/index.ts new file mode 100644 index 0000000..1e5ca81 --- /dev/null +++ b/src/modules/teams/services/index.ts @@ -0,0 +1,5 @@ +export { MeService } from './me.service'; +export { TeamsService } from './teams.service'; +export { TeamMembersService } from './members.service'; +export { TeamsSettingsService } from './settings.service'; +export { TeamInvitationsService } from './invitations.service'; diff --git a/src/modules/teams/services/invitations.service.ts b/src/modules/teams/services/invitations.service.ts new file mode 100644 index 0000000..75bf93a --- /dev/null +++ b/src/modules/teams/services/invitations.service.ts @@ -0,0 +1,412 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { ITeamsRepository } from '../repository'; +import { generateSecret } from 'otplib'; +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; +import { InjectQueue } from '@nestjs/bullmq'; +import { MailJobs, Queues } from '@shared/workers'; +import { Queue } from 'bullmq'; +import { TeamInvitationEvent } from '@shared/workers/events'; +import { InviteMemberDto, UpdateInvitationDto } from '../dtos'; +import { ConfigService } from '@nestjs/config'; +import { BaseException } from '@shared/error'; +import { TeamInvite } from '@core/modules/teams/dtos/invitation.dto'; + +@Injectable() +export class TeamInvitationsService { + private readonly INVITE_TTL = 86400; + private readonly INVITES_KEY = (code: string) => `inv:code:${code}`; + private readonly TEAM_INVITES_KEY = (teamId: string) => `team:invites:${teamId}`; + private readonly USER_INVITES_KEY = (email: string) => `user:invites:${email.toLowerCase()}`; + + private assertCanManageInvites = async (teamId: string, userId: string) => { + const member = await this.teamsRepo.findMember(teamId, userId); + if (!member || (member.role !== 'owner' && member.role !== 'admin')) { + throw new BaseException( + { + code: 'INSUFFICIENT_PERMISSIONS', + message: 'У вас нет прав управлять приглашениями в этой команде', + }, + HttpStatus.FORBIDDEN, + ); + } + return member; + }; + + private parseInvite = (raw: string, code?: string) => { + try { + const invite = JSON.parse(raw) as TeamInvite; + return code ? { code, ...invite } : invite; + } catch { + throw new BaseException( + { + code: 'INVITE_DATA_CORRUPTED', + message: 'Данные приглашения повреждены', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }; + + constructor( + @Inject('ITeamsRepository') + private readonly teamsRepo: ITeamsRepository, + @InjectRedis() + private readonly redis: Redis, + @InjectQueue(Queues.MAIL) + private readonly mailQueue: Queue, + private readonly cfg: ConfigService, + ) {} + + public getInvitations = async (slug: string, userId?: string) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + }, + HttpStatus.NOT_FOUND, + ); + } + + if (userId) { + await this.assertCanManageInvites(team.id, userId); + } + + const codes = await this.redis.smembers(this.TEAM_INVITES_KEY(team.id)); + if (!codes.length) return []; + + const keys = codes.map((c) => this.INVITES_KEY(c)); + const invitesRaw = await this.redis.mget(...keys); + + return invitesRaw + .map((raw, idx) => { + if (!raw) return null; + return this.parseInvite(raw, codes[idx]); + }) + .filter((v): v is TeamInvite => Boolean(v)); + }; + + public getInvitation = async (slug: string, code: string, userId: string) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + }, + HttpStatus.NOT_FOUND, + ); + } + + await this.assertCanManageInvites(team.id, userId); + + const raw = await this.redis.get(this.INVITES_KEY(code)); + if (!raw) { + throw new BaseException( + { + code: 'INVITE_EXPIRED_OR_INVALID', + message: 'Срок действия приглашения истек или код неверен', + }, + HttpStatus.NOT_FOUND, + ); + } + + const invite = this.parseInvite(raw, code); + if (invite.teamId !== team.id) { + throw new BaseException( + { + code: 'INVITE_NOT_FOUND', + message: 'Приглашение не найдено', + }, + HttpStatus.NOT_FOUND, + ); + } + + return invite; + }; + + public updateInvitation = async ( + slug: string, + code: string, + userId: string, + dto: UpdateInvitationDto, + ) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + }, + HttpStatus.NOT_FOUND, + ); + } + + await this.assertCanManageInvites(team.id, userId); + + const key = this.INVITES_KEY(code); + const raw = await this.redis.get(key); + if (!raw) { + throw new BaseException( + { + code: 'INVITE_EXPIRED_OR_INVALID', + message: 'Срок действия приглашения истек или код неверен', + }, + HttpStatus.NOT_FOUND, + ); + } + + const invite = this.parseInvite(raw); + if (invite.teamId !== team.id) { + throw new BaseException( + { + code: 'INVITE_NOT_FOUND', + message: 'Приглашение не найдено', + }, + HttpStatus.NOT_FOUND, + ); + } + + const ttl = await this.redis.ttl(key); + if (ttl === -2) { + throw new BaseException( + { + code: 'INVITE_EXPIRED_OR_INVALID', + message: 'Срок действия приглашения истек или код неверен', + }, + HttpStatus.NOT_FOUND, + ); + } + + invite.role = dto.role; + + if (ttl > 0) { + await this.redis.set(key, JSON.stringify(invite), 'EX', ttl); + } else { + await this.redis.set(key, JSON.stringify(invite)); + } + + return { code, ...invite }; + }; + + public declineInvitation = async (slug: string, code: string, userId: string) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + }, + HttpStatus.NOT_FOUND, + ); + } + + await this.assertCanManageInvites(team.id, userId); + + const raw = await this.redis.get(this.INVITES_KEY(code)); + if (!raw) { + throw new BaseException( + { + code: 'INVITE_EXPIRED_OR_INVALID', + message: 'Срок действия приглашения истек или код неверен', + }, + HttpStatus.NOT_FOUND, + ); + } + + const invite = this.parseInvite(raw); + if (invite.teamId !== team.id) { + throw new BaseException( + { + code: 'INVITE_NOT_FOUND', + message: 'Приглашение не найдено', + }, + HttpStatus.NOT_FOUND, + ); + } + + await this.removeInvitation(team.id, code, invite.email); + + return { + success: true, + message: 'Приглашение удалено', + }; + }; + + public removeInvitation = async (teamId: string, code: string, email: string) => { + try { + const multi = this.redis.multi(); + multi.del(this.INVITES_KEY(code)); + multi.srem(this.TEAM_INVITES_KEY(teamId), code); + multi.srem(this.USER_INVITES_KEY(email.toLowerCase()), code); + await multi.exec(); + } catch { + throw new BaseException( + { + code: 'REDIS_TRANSACTION_FAILED', + message: 'Не удалось удалить приглашение из системы', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + return { success: true }; + }; + + public invite = async (slug: string, inviterId: string, dto: InviteMemberDto) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + }, + HttpStatus.NOT_FOUND, + ); + } + + const inviter = await this.teamsRepo.findMember(team.id, inviterId); + if (!inviter || (inviter.role !== 'owner' && inviter.role !== 'admin')) { + throw new BaseException( + { + code: 'INSUFFICIENT_PERMISSIONS', + message: 'У вас нет прав приглашать новых участников', + }, + HttpStatus.FORBIDDEN, + ); + } + + const code = generateSecret({ length: 8 }); + + const now = new Date(); + const expiresAt = new Date(now.getTime() + this.INVITE_TTL * 1000); + + const inviteData: TeamInvite = { + teamId: team.id, + teamName: team.name, + teamAvatar: team.avatarUrl, + email: dto.email, + role: dto.role || 'member', + inviterId, + inviterName: inviter.firstName, + createdAt: new Date().toISOString(), + expiresAt: expiresAt.toISOString(), + }; + + try { + const multi = this.redis.multi(); + multi.set(this.INVITES_KEY(code), JSON.stringify(inviteData), 'EX', this.INVITE_TTL); + multi.sadd(this.TEAM_INVITES_KEY(team.id), code); + multi.sadd(this.USER_INVITES_KEY(dto.email.toLowerCase()), code); + await multi.exec(); + } catch (error) { + throw new BaseException( + { + code: 'REDIS_TRANSACTION_FAILED', + message: 'Не удалось создать приглашение в системе', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + const origins = this.cfg.get('CORS_ALLOWED_ORIGINS'); + const FRONTEND_URL = origins[0]; + + const event = new TeamInvitationEvent( + dto.email, + team.name, + `${FRONTEND_URL}/invites/accept?code=${code}`, + ); + await this.mailQueue.add(MailJobs.SEND_TEAM_INVITATION, event, { + attempts: 3, + backoff: { + type: 'exponential', + delay: 5000, + }, + }); + + return { + success: true, + message: `Приглашение отправлено на ${dto.email}`, + code, + }; + }; + + public acceptInvite = async (code: string, userId: string, email: string) => { + const inviteRaw = await this.redis.get(this.INVITES_KEY(code)); + if (!inviteRaw) { + throw new BaseException( + { + code: 'INVITE_EXPIRED_OR_INVALID', + message: 'Срок действия приглашения истек или код неверен', + }, + HttpStatus.GONE, + ); + } + + const invite = this.parseInvite(inviteRaw); + + if (invite.email.toLowerCase() !== email.toLowerCase()) { + throw new BaseException( + { + code: 'INVITE_EMAIL_MISMATCH', + message: 'Этот инвайт предназначен для другого почтового адреса', + details: [{ target: 'email', expected: invite.email, actual: email }], + }, + HttpStatus.FORBIDDEN, + ); + } + + const member = await this.teamsRepo.findMember(invite.teamId, userId); + + if (member) { + if (member.status === 'banned') { + throw new BaseException( + { + code: 'MEMBER_BANNED', + message: 'Вы заблокированы в этой команде', + }, + HttpStatus.FORBIDDEN, + ); + } + + if (member.status === 'active') { + throw new BaseException( + { + code: 'ALREADY_MEMBER', + message: 'Вы уже являетесь участником этой команды', + }, + HttpStatus.BAD_REQUEST, + ); + } + } + + try { + await this.teamsRepo.addMember({ + teamId: invite.teamId, + userId, + role: invite.role, + status: 'active', + joinedAt: new Date(), + }); + + await this.removeInvitation(invite.teamId, code, email); + + return { + success: true, + message: 'Вы успешно присоединились к команде', + }; + } catch (error) { + throw new BaseException( + { + code: 'ACCEPT_INVITE_FAILED', + message: 'Ошибка при вступлении в команду', + details: [{ reason: error instanceof Error ? error.message : 'DB Error' }], + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }; +} diff --git a/src/modules/teams/services/me.service.ts b/src/modules/teams/services/me.service.ts new file mode 100644 index 0000000..e0012b6 --- /dev/null +++ b/src/modules/teams/services/me.service.ts @@ -0,0 +1,32 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ITeamsRepository } from '../repository'; +import { TeamMemberMapper } from '../mappers'; +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; + +@Injectable() +export class MeService { + constructor( + @Inject('ITeamsRepository') + private readonly teamsRepo: ITeamsRepository, + @InjectRedis() + private readonly redis: Redis, + ) {} + + public getMyInvites = async (email: string) => { + const codes = await this.redis.smembers(`user:invites:${email}`); + + if (!codes.length) return []; + + const results = await this.redis.mget(codes.map((c) => `inv:code:${c}`)); + + return results + .map((raw, i) => TeamMemberMapper.toPublicInvite(raw, codes[i])) + .filter(Boolean); + }; + + public getAll = async (userId: string, pagination: Record) => { + const teams = await this.teamsRepo.findByUser(userId, pagination); + return teams.map((t) => TeamMemberMapper.toUserTeam(t)); + }; +} diff --git a/src/modules/teams/services/members.service.ts b/src/modules/teams/services/members.service.ts new file mode 100644 index 0000000..9fca6e9 --- /dev/null +++ b/src/modules/teams/services/members.service.ts @@ -0,0 +1,212 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { ITeamsRepository } from '../repository'; +import type { UpdateMemberDto } from '../dtos'; +import { TeamMemberMapper } from '../mappers'; +import { BaseException } from '@shared/error'; +import { ROLE_PRIORITY } from '@shared/constants'; + +@Injectable() +export class TeamMembersService { + constructor( + @Inject('ITeamsRepository') + private readonly teamsRepo: ITeamsRepository, + ) {} + + public getMembers = async (slug: string) => { + const team = await this.teamsRepo.findBySlug(slug); + + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: `Команда ${slug} не найдена`, + }, + HttpStatus.NOT_FOUND, + ); + } + + const members = await this.teamsRepo.findMembers(team.id); + return TeamMemberMapper.toList(members); + }; + + public updateMember = async ( + slug: string, + currentUserId: string, + targetUserId: string, + dto: UpdateMemberDto, + ) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: `Команда ${slug} не найдена`, + }, + HttpStatus.NOT_FOUND, + ); + } + + const [currentUser, targetUser] = await Promise.all([ + this.teamsRepo.findMember(team.id, currentUserId), + this.teamsRepo.findMember(team.id, targetUserId), + ]); + + if (!currentUser || !targetUser) { + throw new BaseException( + { + code: 'MEMBER_NOT_FOUND', + message: 'Участник не найден', + }, + HttpStatus.NOT_FOUND, + ); + } + + if (ROLE_PRIORITY[currentUser.role] < ROLE_PRIORITY.admin) { + throw new BaseException( + { + code: 'ADMIN_ROLE_REQUIRED', + message: 'У вас нет прав на редактирование участников', + }, + HttpStatus.FORBIDDEN, + ); + } + + // Нельзя менять роль тому, кто выше тебя или равен тебе по весу + if ( + currentUserId !== targetUserId && + ROLE_PRIORITY[currentUser.role] <= ROLE_PRIORITY[targetUser.role] + ) { + throw new BaseException( + { + code: 'INSUFFICIENT_RANK', + message: 'Вы не можете менять данные участника с равным или высшим рангом', + details: [{ currentRole: currentUser.role, targetRole: targetUser.role }], + }, + HttpStatus.FORBIDDEN, + ); + } + + // Защита от потери овнера: нельзя разжаловать овнера в админа + if (targetUser.role === 'owner' && dto.role && dto.role !== 'owner') { + throw new BaseException( + { + code: 'OWNER_PROTECTION_VIOLATION', + message: + 'Нельзя изменить роль владельца через это меню. Используйте передачу прав.', + }, + HttpStatus.BAD_REQUEST, + ); + } + + // Нельзя назначить роль выше своей (Админ не может сделать кого-то Овнером) + if ( + dto.role && + ROLE_PRIORITY[dto.role] >= ROLE_PRIORITY[currentUser.role] && + currentUser.role !== 'owner' + ) { + throw new BaseException( + { + code: 'CANNOT_ASSIGN_HIGHER_ROLE', + message: 'Вы не можете назначить роль выше своей или равную своей', + }, + HttpStatus.FORBIDDEN, + ); + } + + try { + const result = await this.teamsRepo.updateMember(team.id, targetUserId, dto); + return { + success: result, + message: `Данные участника команды "${team.name}" успешно обновлены`, + }; + } catch (error) { + throw new BaseException( + { + code: 'MEMBER_UPDATE_FAILED', + message: 'Ошибка при обновлении данных участника', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }; + + public removeMember = async (slug: string, currentUserId: string, targetUserId: string) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: `Команда ${slug} не найдена`, + }, + HttpStatus.NOT_FOUND, + ); + } + + const [currentUser, targetUser] = await Promise.all([ + this.teamsRepo.findMember(team.id, currentUserId), + this.teamsRepo.findMember(team.id, targetUserId), + ]); + + if (!targetUser) { + throw new BaseException( + { code: 'MEMBER_NOT_FOUND', message: 'Участник не найден' }, + HttpStatus.NOT_FOUND, + ); + } + if (!currentUser) { + throw new BaseException( + { code: 'NOT_A_TEAM_MEMBER', message: 'Вы не состоите в этой команде' }, + HttpStatus.FORBIDDEN, + ); + } + + const isSelfRemoval = currentUserId === targetUserId; + + if (isSelfRemoval) { + if (currentUser.role === 'owner') { + throw new BaseException( + { + code: 'OWNER_CANNOT_LEAVE', + message: + 'Владелец не может покинуть команду. Передайте права или удалите команду.', + }, + HttpStatus.BAD_REQUEST, + ); + } + } else { + const canKick = ROLE_PRIORITY[currentUser.role] > ROLE_PRIORITY[targetUser.role]; + const hasAuthority = ROLE_PRIORITY[currentUser.role] >= ROLE_PRIORITY.admin; + + if (!hasAuthority || !canKick) { + throw new BaseException( + { + code: 'KICK_FORBIDDEN', + message: 'У вас недостаточно прав, чтобы исключить этого участника', + details: [ + { reason: !hasAuthority ? 'Low authority' : 'Target rank too high' }, + ], + }, + HttpStatus.FORBIDDEN, + ); + } + } + + try { + const result = await this.teamsRepo.removeMember(team.id, targetUserId); + return { + success: result, + message: isSelfRemoval + ? `Вы успешно покинули команду ${team.name}` + : `Участник успешно исключен из команды ${team.name}`, + }; + } catch (error) { + throw new BaseException( + { + code: 'MEMBER_REMOVAL_FAILED', + message: 'Ошибка при удалении участника', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }; +} diff --git a/src/modules/teams/services/settings.service.ts b/src/modules/teams/services/settings.service.ts new file mode 100644 index 0000000..15ee711 --- /dev/null +++ b/src/modules/teams/services/settings.service.ts @@ -0,0 +1,82 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { ITeamsRepository } from '../repository'; +import { ITeamMedia, TEAM_MEDIA_TOKEN, type FileUploadDto } from '../../media'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class TeamsSettingsService { + constructor( + @Inject('ITeamsRepository') + private readonly teamsRepo: ITeamsRepository, + @Inject(TEAM_MEDIA_TOKEN) + private readonly mediaService: ITeamMedia, + ) {} + + public updateTeamAvatar = async (slug: string, fileDto: FileUploadDto) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + details: [{ target: 'slug', value: slug }], + }, + HttpStatus.NOT_FOUND, + ); + } + + return this.mediaService.uploadTeamAvatar(team.id, fileDto, (url) => + this.teamsRepo.updateTeamAvatar(team.id, url), + ); + }; + + public updateTeamBanner = async (slug: string, fileDto: FileUploadDto) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + details: [{ target: 'slug', value: slug }], + }, + HttpStatus.NOT_FOUND, + ); + } + + return this.mediaService.uploadTeamBanner(team.id, fileDto, (url) => + this.teamsRepo.updateTeamBanner(team.id, url), + ); + }; + + public syncTags = async (slug: string, tags: string[]) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: 'Команда не найдена', + }, + HttpStatus.NOT_FOUND, + ); + } + + const normalizedTags = [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))]; + const isSynced = await this.teamsRepo.syncTags(team.id, normalizedTags); + + if (!isSynced) { + throw new BaseException( + { + code: 'TAGS_SYNC_FAILED', + message: 'Не удалось обновить теги команды. Попробуйте позже.', + details: [{ target: 'tags', count: normalizedTags.length }], + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + return { + success: true, + message: 'Теги команды обновлены', + }; + }; +} diff --git a/src/modules/teams/services/teams.service.ts b/src/modules/teams/services/teams.service.ts new file mode 100644 index 0000000..f56ce3f --- /dev/null +++ b/src/modules/teams/services/teams.service.ts @@ -0,0 +1,217 @@ +import { Inject, Injectable, HttpStatus } from '@nestjs/common'; +import { ITeamsRepository } from '../repository'; +import { FindTagsQuery } from '../dtos'; +import type { CreateTeamDto, UpdateTeamDto } from '../dtos'; +import { slugify } from 'transliteration'; +import { TeamMemberMapper } from '../mappers'; +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class TeamsService { + constructor( + @Inject('ITeamsRepository') + private readonly teamsRepo: ITeamsRepository, + @InjectRedis() + private readonly redis: Redis, + ) {} + + public checkSlug = async (slug: string) => { + const available = await this.teamsRepo.isSlugAvailable(slug); + return { available }; + }; + + public getMyInvites = async (email: string) => { + const codes = await this.redis.smembers(`user:invites:${email}`); + + if (!codes.length) return []; + + const results = await this.redis.mget(codes.map((c) => `inv:code:${c}`)); + + return results + .map((raw, i) => TeamMemberMapper.toPublicInvite(raw, codes[i])) + .filter(Boolean); + }; + + public create = async (userId: string, dto: CreateTeamDto) => { + const baseSlug = slugify(dto.slug || dto.name, { lowercase: true, separator: '-' }); + const existingTeam = await this.teamsRepo.findBySlug(baseSlug); + + if (existingTeam) { + throw new BaseException( + { + code: 'SLUG_ALREADY_EXISTS', + message: `Ссылка "${baseSlug}" уже занята другой командой`, + details: [{ target: 'slug', value: baseSlug }], + }, + HttpStatus.CONFLICT, + ); + } + + const { tags, ...teamData } = dto; + const uniqueTags = tags ? [...new Set(tags.map((tag) => tag.toLowerCase()))] : []; + + try { + const result = await this.teamsRepo.create( + userId, + { + ...teamData, + slug: baseSlug, + }, + uniqueTags, + ); + + return { + ...result, + slug: baseSlug, + message: 'Команда успешно создана', + }; + } catch (error) { + throw new BaseException( + { + code: 'TEAM_CREATE_FAILED', + message: 'Не удалось создать команду', + details: [{ reason: error instanceof Error ? error.message : 'Unknown error' }], + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }; + + public update = async (slug: string, userId: string, dto: UpdateTeamDto) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: `Команда ${slug} не найдена`, + }, + HttpStatus.NOT_FOUND, + ); + } + + const member = await this.teamsRepo.findMember(team.id, userId); + + const canEdit = member?.role === 'admin' || member?.role === 'owner'; + + if (!canEdit) { + throw new BaseException( + { + code: 'INSUFFICIENT_PERMISSIONS', + message: 'У вас нет прав для редактирования этой команды', + details: [{ target: 'role', value: member?.role }], + }, + HttpStatus.FORBIDDEN, + ); + } + + const { tags, ...data } = dto; + + try { + const result = await this.teamsRepo.update(team.id, data, tags); + + return { + ...result, + message: 'Данные команды успешно обновлены', + }; + } catch (error) { + throw new BaseException( + { + code: 'TEAM_UPDATE_FAILED', + message: 'Ошибка при обновлении данных команды', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }; + + public remove = async (slug: string, userId: string) => { + const team = await this.teamsRepo.findBySlug(slug); + + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: `Команда ${slug} не найдена`, + }, + HttpStatus.NOT_FOUND, + ); + } + + const member = await this.teamsRepo.findMember(team.id, userId); + + const canDelete = team.ownerId === userId || member?.role === 'owner'; + + if (!canDelete) { + throw new BaseException( + { + code: 'ONLY_OWNER_CAN_DELETE', + message: 'Только владелец может удалить команду', + }, + HttpStatus.FORBIDDEN, + ); + } + + try { + const result = await this.teamsRepo.remove(team.id, userId); + + return { + success: result, + message: 'Данные команды успешно обновлены', + }; + } catch (error) { + throw new BaseException( + { + code: 'TEAM_DELETE_FAILED', + message: 'Не удалось удалить команду', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }; + + public getAllTags = async (query: FindTagsQuery) => { + const safePage = Math.max(query.page ?? 1, 1); + const safeLimit = Math.min(Math.max(query.limit ?? 20, 1), 50); + const offset = (safePage - 1) * safeLimit; + + const { data, total } = await this.teamsRepo.findAllTags({ + search: query.search, + limit: safeLimit, + offset, + }); + + const totalPages = total === 0 ? 0 : Math.ceil(total / safeLimit); + return { + data, + meta: { + hasNextPage: safePage < totalPages, + hasPrevPage: safePage > 1, + total, + totalPages, + page: safePage, + limit: safeLimit, + }, + }; + }; + + public getAll = async (userId: string, pagination: Record) => { + const teams = await this.teamsRepo.findByUser(userId, pagination); + return teams.map((t) => TeamMemberMapper.toUserTeam(t)); + }; + + public getOne = async (slug: string) => { + const team = await this.teamsRepo.findBySlug(slug); + if (!team) { + throw new BaseException( + { + code: 'TEAM_NOT_FOUND', + message: `Команда ${slug} не найдена`, + }, + HttpStatus.NOT_FOUND, + ); + } + return team; + }; +} diff --git a/src/modules/teams/teams.module.ts b/src/modules/teams/teams.module.ts new file mode 100644 index 0000000..708f2b6 --- /dev/null +++ b/src/modules/teams/teams.module.ts @@ -0,0 +1,79 @@ +import { Module } from '@nestjs/common'; +import { + TeamsInvitationsController, + TeamsSettingsController, + TeamsMembersController, + TeamsController, + MeController, +} from './controller'; +import { MediaModule } from '../media'; +import { + MeService, + TeamsService, + TeamMembersService, + TeamsSettingsService, + TeamInvitationsService, +} from './services'; +import { TeamsRepository } from './repository'; +import { RedisModule } from '@nestjs-modules/ioredis'; +import { ConfigService } from '@nestjs/config'; +import { BullModule } from '@nestjs/bullmq'; +import { Queues } from '@shared/workers'; +import { BullBoardModule } from '@bull-board/nestjs'; +import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; +import { FindTeamCommand, FindTeamMemberCommand } from './commands'; + +const REPOSITORY = { provide: 'ITeamsRepository', useClass: TeamsRepository }; + +@Module({ + imports: [ + MediaModule, + RedisModule.forRootAsync({ + inject: [ConfigService], + useFactory: async (cfg: ConfigService) => { + const host = cfg.getOrThrow('REDIS_HOST', { infer: true }); + const port = cfg.get('REDIS_PORT'); + const password = cfg.get('REDIS_PASSWORD'); + const url = `redis://${host}${port ? `:${port}` : ''}`; + + return { + type: 'single', + url, + options: { + password, + retryStrategy(times) { + return Math.min(times * 50, 2000); + }, + commandTimeout: 3000, + }, + }; + }, + }), + BullModule.registerQueue({ + name: Queues.MAIL, + }), + BullBoardModule.forFeature({ + name: Queues.MAIL, + adapter: BullMQAdapter, + }), + ], + controllers: [ + TeamsInvitationsController, + TeamsSettingsController, + TeamsMembersController, + TeamsController, + MeController, + ], + providers: [ + REPOSITORY, + MeService, + TeamsService, + TeamMembersService, + TeamsSettingsService, + TeamInvitationsService, + FindTeamCommand, + FindTeamMemberCommand, + ], + exports: [FindTeamCommand, FindTeamMemberCommand], +}) +export class TeamsModule {} diff --git a/src/shared/adapters/mail/adapter.ts b/src/shared/adapters/mail/adapter.ts new file mode 100644 index 0000000..12362a3 --- /dev/null +++ b/src/shared/adapters/mail/adapter.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import * as nodemailer from 'nodemailer'; +import * as hbs from 'handlebars'; +import * as fs from 'fs'; +import * as path from 'path'; +import { IMailPort } from './port'; + +@Injectable() +export class MailAdapter implements IMailPort { + private transporter: nodemailer.Transporter; + + constructor(private cfg: ConfigService) { + this.transporter = nodemailer.createTransport({ + host: this.cfg.get('MAIL_HOST'), + port: this.cfg.get('MAIL_PORT'), + secure: true, + auth: { + user: this.cfg.get('MAIL_USER'), + pass: this.cfg.get('MAIL_PASSWORD'), + }, + }); + } + + private async sendMail(to: string, subject: string, templateName: string, context: any) { + const templatePath = path.join(process.cwd(), 'templates', `${templateName}.hbs`); + const templateSource = fs.readFileSync(templatePath, 'utf8'); + + const contextWithYear = { + ...context, + year: new Date().getFullYear(), + }; + + const template = hbs.compile(templateSource); + const html = template(contextWithYear); + + return await this.transporter.sendMail({ + from: `"${this.cfg.get('MAIL_FROM_NAME')}" <${this.cfg.get('MAIL_FROM_EMAIL')}>`, + to, + subject, + html, + }); + } + + async sendRegistrationCode(email: string, name: string, code: string) { + const codeArray = code.toString().split(''); + + return this.sendMail(email, 'Код подтверждения регистрации', 'confirmation', { + name, + codeArray, + }); + } + + async sendResetPasswordCode(email: string, code: string) { + const codeArray = code.toString().split(''); + + return this.sendMail(email, 'Восстановление пароля', 'reset-password', { + codeArray, + }); + } + + async sendTeamInvitation(email: string, teamName: string, inviteUrl: string) { + return this.sendMail(email, `Приглашение в команду ${teamName}`, 'team-invitation', { + teamName, + inviteUrl, + }); + } +} diff --git a/src/shared/adapters/mail/index.ts b/src/shared/adapters/mail/index.ts new file mode 100644 index 0000000..e132652 --- /dev/null +++ b/src/shared/adapters/mail/index.ts @@ -0,0 +1,3 @@ +export { MailAdapter } from './adapter'; +export { IMailPort } from './port'; +export { MailModule } from './module'; diff --git a/src/shared/adapters/mail/module.ts b/src/shared/adapters/mail/module.ts new file mode 100644 index 0000000..50174b7 --- /dev/null +++ b/src/shared/adapters/mail/module.ts @@ -0,0 +1,16 @@ +import { Global, Module } from '@nestjs/common'; +import { MailAdapter } from './adapter'; +import { MailProcessor } from '@shared/workers'; + +@Global() +@Module({ + providers: [ + { + provide: 'IMailPort', + useClass: MailAdapter, + }, + MailProcessor, + ], + exports: ['IMailPort'], +}) +export class MailModule {} diff --git a/src/shared/adapters/mail/port.ts b/src/shared/adapters/mail/port.ts new file mode 100644 index 0000000..0ae1a57 --- /dev/null +++ b/src/shared/adapters/mail/port.ts @@ -0,0 +1,5 @@ +export interface IMailPort { + sendRegistrationCode(email: string, name: string, code: string): Promise; + sendResetPasswordCode(email: string, code: string): Promise; + sendTeamInvitation(email: string, teamName: string, inviteUrl: string): Promise; +} diff --git a/src/shared/constants/file.constants.ts b/src/shared/constants/file.constants.ts new file mode 100644 index 0000000..be950f2 --- /dev/null +++ b/src/shared/constants/file.constants.ts @@ -0,0 +1 @@ +export const IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/jpg']; diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts new file mode 100644 index 0000000..8a4ea9d --- /dev/null +++ b/src/shared/constants/index.ts @@ -0,0 +1,2 @@ +export * from './file.constants'; +export * from './roles.constant'; diff --git a/src/shared/constants/roles.constant.ts b/src/shared/constants/roles.constant.ts new file mode 100644 index 0000000..1da5f2c --- /dev/null +++ b/src/shared/constants/roles.constant.ts @@ -0,0 +1,7 @@ +export const ROLE_PRIORITY: Record = { + owner: 4, + admin: 3, + moderator: 2, + member: 1, + viewer: 0, +}; diff --git a/src/shared/decorators/api-controller.decorator.ts b/src/shared/decorators/api-controller.decorator.ts new file mode 100644 index 0000000..bcbb4af --- /dev/null +++ b/src/shared/decorators/api-controller.decorator.ts @@ -0,0 +1,19 @@ +import { Controller, UseGuards, applyDecorators } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { ApiErrorResponse } from '@shared/error'; +import { BearerAuthGuard } from '../guards'; + +export const ApiBaseController = (path: string, tag: string, hasJWTGuard?: boolean) => { + const decorators = [ + ApiTags(tag), + Controller(path), + hasJWTGuard ? UseGuards(BearerAuthGuard) : null, + ApiErrorResponse( + 500, + 'INTERNAL_SERVER_ERROR', + 'Произошла критическая ошибка на стороне сервера', + ), + ].filter(Boolean); + + return applyDecorators(...decorators); +}; diff --git a/src/shared/decorators/extract-fastify-file.decorator.ts b/src/shared/decorators/extract-fastify-file.decorator.ts new file mode 100644 index 0000000..05efe78 --- /dev/null +++ b/src/shared/decorators/extract-fastify-file.decorator.ts @@ -0,0 +1,63 @@ +import { createParamDecorator, type ExecutionContext, HttpStatus } from '@nestjs/common'; +import type { FastifyRequest } from 'fastify'; +import { IMAGE_MIME_TYPES } from '../constants'; +import type { FileUploadDto } from '../../modules/media'; +import { BaseException } from '@shared/error'; + +export const ExtractFastifyFile = createParamDecorator( + async ( + data: { allowedMimetypes?: string[] } = { allowedMimetypes: IMAGE_MIME_TYPES }, + ctx: ExecutionContext, + ): Promise => { + const req = ctx.switchToHttp().getRequest(); + + if (!req.isMultipart()) { + throw new BaseException( + { + code: 'INVALID_CONTENT_TYPE', + message: 'Ожидался multipart/form-data запрос', + details: [ + { target: 'header', message: 'Content-Type must be multipart/form-data' }, + ], + }, + HttpStatus.BAD_REQUEST, + ); + } + + const file = await req.file(); + if (!file) { + throw new BaseException( + { + code: 'FILE_NOT_FOUND', + message: 'Файл не был передан в запросе', + }, + HttpStatus.BAD_REQUEST, + ); + } + + if (data?.allowedMimetypes && !data.allowedMimetypes.includes(file.mimetype)) { + throw new BaseException( + { + code: 'INVALID_FILE_TYPE', + message: 'Недопустимый формат файла', + details: [ + { + target: 'mimetype', + received: file.mimetype, + expected: data.allowedMimetypes, + }, + ], + }, + HttpStatus.BAD_REQUEST, + ); + } + + const buffer = await file.toBuffer(); + + return { + buffer, + filename: file.filename, + mimetype: file.mimetype, + }; + }, +); diff --git a/src/shared/decorators/index.ts b/src/shared/decorators/index.ts new file mode 100644 index 0000000..33aabf6 --- /dev/null +++ b/src/shared/decorators/index.ts @@ -0,0 +1,4 @@ +export { ApiBaseController } from './api-controller.decorator'; +export * from './user.decorator'; +export { ExtractFastifyFile } from './extract-fastify-file.decorator'; +export { IS_PUBLIC_KEY, Public } from './public.decorator'; diff --git a/src/shared/decorators/public.decorator.ts b/src/shared/decorators/public.decorator.ts new file mode 100644 index 0000000..b3845e1 --- /dev/null +++ b/src/shared/decorators/public.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; + +export const IS_PUBLIC_KEY = 'isPublic'; +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/src/shared/decorators/user.decorator.ts b/src/shared/decorators/user.decorator.ts new file mode 100644 index 0000000..938bc37 --- /dev/null +++ b/src/shared/decorators/user.decorator.ts @@ -0,0 +1,20 @@ +import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; +import type { FastifyRequest } from 'fastify'; +import type { JwtPayload } from '@shared/types'; + +export const GetUser = createParamDecorator( + (data: keyof JwtPayload | undefined, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + const user = request.user; + if (!user) return null; + return data ? user[data] : user; + }, +); + +export const GetUserId = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): string | undefined => { + const request = ctx.switchToHttp().getRequest(); + const user = request.user; + return user?.sub; + }, +); diff --git a/src/shared/dtos/index.ts b/src/shared/dtos/index.ts new file mode 100644 index 0000000..5a8e94b --- /dev/null +++ b/src/shared/dtos/index.ts @@ -0,0 +1,2 @@ +export * from './pagination.dto'; +export * from './response.dto'; diff --git a/src/shared/dtos/pagination.dto.ts b/src/shared/dtos/pagination.dto.ts new file mode 100644 index 0000000..d0e8d38 --- /dev/null +++ b/src/shared/dtos/pagination.dto.ts @@ -0,0 +1,9 @@ +import { z } from 'zod/v4'; +import { createZodDto } from 'nestjs-zod'; + +export const PaginationSchema = z.object({ + page: z.coerce.number().int().min(1).default(1), + limit: z.coerce.number().int().min(1).max(100).default(20), +}); + +export class PaginationDto extends createZodDto(PaginationSchema) {} diff --git a/src/shared/dtos/response.dto.ts b/src/shared/dtos/response.dto.ts new file mode 100644 index 0000000..325f719 --- /dev/null +++ b/src/shared/dtos/response.dto.ts @@ -0,0 +1,9 @@ +import { z } from 'zod/v4'; +import { createZodDto } from 'nestjs-zod'; + +export const ActionResponseSchema = z.object({ + success: z.boolean().describe('Статус операции'), + message: z.string().optional().describe('Сообщение для пользователя'), +}); + +export class ActionResponse extends createZodDto(ActionResponseSchema) {} diff --git a/src/shared/entities/index.ts b/src/shared/entities/index.ts new file mode 100644 index 0000000..61f7880 --- /dev/null +++ b/src/shared/entities/index.ts @@ -0,0 +1,5 @@ +export { baseSchema } from './schema'; +export * from '../../user/infrastructure/persistence/models'; +export * from '../../auth/infrastructure/persistence/models'; +export * from '../../modules/teams/entities'; +export * from '../../modules/projects/entities'; diff --git a/src/shared/entities/schema.ts b/src/shared/entities/schema.ts new file mode 100644 index 0000000..8a9bcc9 --- /dev/null +++ b/src/shared/entities/schema.ts @@ -0,0 +1,3 @@ +import { pgSchema, type PgSchema } from 'drizzle-orm/pg-core'; + +export const baseSchema: PgSchema = pgSchema('base'); diff --git a/src/shared/error/exception.ts b/src/shared/error/exception.ts new file mode 100644 index 0000000..640645f --- /dev/null +++ b/src/shared/error/exception.ts @@ -0,0 +1,18 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; + +interface IDetailsOptions { + target?: string; + [key: string]: any; +} + +export interface IErrorOptions { + code: string; + message: string; + details?: IDetailsOptions[]; +} + +export class BaseException extends HttpException { + constructor(options: IErrorOptions, status: HttpStatus) { + super(options, status); + } +} diff --git a/src/shared/error/filter.ts b/src/shared/error/filter.ts new file mode 100644 index 0000000..2a8b778 --- /dev/null +++ b/src/shared/error/filter.ts @@ -0,0 +1,183 @@ +import { + type ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { ZodValidationException } from 'nestjs-zod'; +import type { FastifyReply, FastifyRequest } from 'fastify'; +import { DatabaseError } from 'pg'; +import { BaseException, IErrorOptions } from './exception'; +import { DrizzleQueryError } from 'drizzle-orm'; +import type { ZodError, ZodIssue } from 'zod/v4'; +import { DATABASE_ERRORS } from './swagger'; + +@Catch() +export class GlobalExceptionFilter implements ExceptionFilter { + private isDev = process.env.NODE_ENV === 'development'; + + catch(exception: unknown, host: ArgumentsHost) { + if (exception instanceof ZodValidationException) { + return this.parseZodValidation(exception, host); + } + + if (exception instanceof BaseException) { + return this.parseHttp(exception, host); + } + + if (exception instanceof HttpException) { + return this.parseNestHttp(exception, host); + } + + if (exception instanceof DrizzleQueryError) { + return this.parseDatabase(exception, host); + } + + return this.handleUnknownError(exception, host); + } + + private parseZodValidation = async (exception: ZodValidationException, host: ArgumentsHost) => { + const { request, response } = this.getCtxBase(host); + const status = exception.getStatus(); + + const zodError = exception.getZodError() as ZodError; + const issues: ZodIssue[] = zodError.issues || []; + + return response.status(status).send( + this.formatErrorResponse(request, status, { + code: 'VALIDATION_FAILED', + message: 'Переданные данные не прошли валидацию', + details: issues, + stack: exception.stack, + }), + ); + }; + + private parseDatabase = async (exception: DrizzleQueryError, host: ArgumentsHost) => { + const { request, response } = this.getCtxBase(host); + + const error = + exception.cause instanceof DatabaseError + ? exception.cause + : exception instanceof DatabaseError + ? exception + : null; + + let status = 500; + let message = exception.message || 'Database operation failed'; + const errorCode = 'DATABASE_ERROR'; + + if (error) { + const mapping = DATABASE_ERRORS[error.code]; + if (mapping) { + status = mapping.code; + message = mapping.msg; + } + } + + return response.status(status).send( + this.formatErrorResponse(request, status, { + code: errorCode, + message, + details: error?.constraint ? [{ target: error.constraint }] : [], + stack: exception.stack, + service: 'postgres', + }), + ); + }; + + private parseHttp = async (exception: BaseException, host: ArgumentsHost) => { + const { request, response } = this.getCtxBase(host); + const status = exception.getStatus(); + + const error = exception.getResponse() as IErrorOptions; + + return response.status(status).send( + this.formatErrorResponse(request, status, { + code: error.code, + message: error.message || exception.message, + details: error.details || [], + stack: exception.stack, + }), + ); + }; + + private parseNestHttp = async (exception: HttpException, host: ArgumentsHost) => { + const { request, response } = this.getCtxBase(host); + const status = exception.getStatus(); + const res = exception.getResponse(); + + const message = + typeof res === 'object' && res['message'] ? res['message'] : exception.message; + + const code = + typeof res === 'object' && res['error'] + ? res['error'].toUpperCase().replace(/\s+/g, '_') + : 'HTTP_EXCEPTION'; + + return response.status(status).send( + this.formatErrorResponse(request, status, { + code, + message, + stack: exception.stack, + details: [], + }), + ); + }; + + private handleUnknownError(exception: any, host: ArgumentsHost) { + const { request, response } = this.getCtxBase(host); + const status = HttpStatus.INTERNAL_SERVER_ERROR; + + return response.status(status).send( + this.formatErrorResponse(request, status, { + code: 'INTERNAL_SERVER_ERROR', + message: 'Произошла непредвиденная ошибка на сервере', + details: [], + stack: exception?.stack, + }), + ); + } + + private formatErrorResponse( + request: FastifyRequest, + status: number, + data: { code: string; message: string; details: any[]; stack?: string; service?: string }, + ) { + const requestId = request.id ?? request.headers['x-request-id']; + + return { + success: false, + error: { + code: data.code, + message: data.message, + retryable: status >= 500, + }, + details: data.details, + meta: { + service: data.service ?? 'gateway', + request: { + requestId, + path: request.url, + method: request.method, + ip: request.ip, + }, + timestamp: new Date().toISOString(), + ...(this.isDev && { + debug: { + stack: data.stack, + }, + }), + }, + }; + } + + private getCtxBase(host: ArgumentsHost) { + const ctx = host.switchToHttp(); + return { + response: ctx.getResponse(), + request: ctx.getRequest(), + }; + } +} diff --git a/src/shared/error/index.ts b/src/shared/error/index.ts new file mode 100644 index 0000000..9ddc922 --- /dev/null +++ b/src/shared/error/index.ts @@ -0,0 +1,3 @@ +export * from './swagger'; +export * from './filter'; +export * from './exception'; diff --git a/src/shared/error/schema.ts b/src/shared/error/schema.ts new file mode 100644 index 0000000..e064c5c --- /dev/null +++ b/src/shared/error/schema.ts @@ -0,0 +1,37 @@ +import { z } from 'zod'; +import { createZodDto } from 'nestjs-zod'; + +const ErrorDetailSchema = z.object({ + field: z.string().describe('Путь к полю (например, "user.email")'), + message: z.string().describe('Сообщение об ошибке'), + code: z.string().describe('Машиночитаемый код (например, "too_short")'), +}); + +const ErrorMetaSchema = z.object({ + service: z.string().default('gateway').describe('Имя микросервиса'), + request: z.object({ + requestId: z.string().describe('Trace ID для логов'), + path: z.string().describe('URL эндпоинта'), + method: z.string().describe('HTTP метод'), + ip: z.string().optional().describe('IP клиента'), + }), + timestamp: z.string().datetime().describe('Время ошибки ISO 8601'), + debug: z + .object({ + stack: z.string().optional().describe('Стек вызовов (только в Dev)'), + }) + .optional(), +}); + +export const GlobalErrorSchema = z.object({ + success: z.literal(false).default(false), + error: z.object({ + code: z.string().describe('Бизнес-код ошибки'), + message: z.string().describe('Описание для пользователя'), + retryable: z.boolean().describe('Флаг возможности повтора'), + }), + details: z.array(ErrorDetailSchema).optional(), + meta: ErrorMetaSchema, +}); + +export class GlobalErrorResponse extends createZodDto(GlobalErrorSchema) {} diff --git a/src/shared/error/swagger.ts b/src/shared/error/swagger.ts new file mode 100644 index 0000000..57489c9 --- /dev/null +++ b/src/shared/error/swagger.ts @@ -0,0 +1,66 @@ +import { ApiResponse, getSchemaPath } from '@nestjs/swagger'; +import { GlobalErrorResponse } from './schema'; +import { applyDecorators } from '@nestjs/common'; + +export const ApiErrorResponse = ( + status: number, + bizCode: string, + description: string, + details?: { field: string; message: string; code: string }[], +) => + ApiResponse({ + status, + description, + schema: { + allOf: [{ $ref: getSchemaPath(GlobalErrorResponse.Output) }], + example: { + success: false, + error: { + code: bizCode, + message: description, + retryable: status >= 500, + }, + details: details || [], + meta: { + request: { + requestId: 'req-clj1abc230000jk78', + path: '/api/v1/...', + method: 'POST', + ip: '127.0.0.1', + }, + timestamp: new Date().toISOString(), + service: 'main-backend', + }, + }, + }, + }); + +export const ApiBadRequest = (description: string = 'Некорректный запрос') => + applyDecorators(ApiErrorResponse(400, 'BAD_REQUEST', description)); + +export const ApiUnauthorized = (description: string = 'Сессия истекла или токен не валиден') => + applyDecorators(ApiErrorResponse(401, 'AUTH_REQUIRED', description)); + +export const ApiForbidden = (description: string = 'У вас недостаточно прав для этого действия') => + applyDecorators(ApiErrorResponse(403, 'ACCESS_DENIED', description)); + +export const ApiNotFound = (description: string = 'Ресурс не найден') => + applyDecorators(ApiErrorResponse(404, 'NOT_FOUND', description)); + +export const ApiValidationError = ( + description: string = 'Ошибка валидации входных данных', + fields: any[] = [], +) => applyDecorators(ApiErrorResponse(400, 'VALIDATION_FAILED', description, fields)); + +export const ApiConflict = (description: string = 'Ресурс уже существует') => + applyDecorators(ApiErrorResponse(409, 'CONFLICT', description)); + +export const DATABASE_ERRORS: Record = { + '23505': { code: 409, msg: 'Запись с таким значением уже существует (дубликат).' }, + '23503': { code: 409, msg: 'Ошибка внешнего ключа: связанная запись не найдена.' }, + '22P02': { code: 400, msg: 'Неверный формат данных (например, некорректный UUID).' }, + '23514': { code: 400, msg: 'Нарушено ограничение проверки (check constraint).' }, + '23502': { code: 400, msg: 'Отсутствует обязательное поле.' }, + '08006': { code: 500, msg: 'Ошибка соединения с базой данных.' }, + '40001': { code: 500, msg: 'Конфликт транзакции. Пожалуйста, повторите попытку.' }, +}; diff --git a/src/shared/guards/bearer.guard.ts b/src/shared/guards/bearer.guard.ts new file mode 100644 index 0000000..a7b2b02 --- /dev/null +++ b/src/shared/guards/bearer.guard.ts @@ -0,0 +1,69 @@ +import { type ExecutionContext, HttpStatus, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { AuthGuard } from '@nestjs/passport'; +import { IS_PUBLIC_KEY } from '@shared/decorators'; +import { BaseException } from '@shared/error'; +import type { JwtPayload } from '@shared/types'; +import type { FastifyRequest } from 'fastify'; + +@Injectable() +export class BearerAuthGuard extends AuthGuard('bearer') { + constructor(private reflector: Reflector) { + super(); + } + + async canActivate(context: ExecutionContext): Promise { + try { + return super.canActivate(context) as Promise; + } catch (e) { + if (this.isPublicOrHasToken(context)) { + return true; + } + + throw e; + } + } + + handleRequest( + err: unknown, + user: TUser, + info: unknown, + context: ExecutionContext, + ): TUser { + if (user) { + return user; + } + + if (this.isPublicOrHasToken(context)) { + return null; + } + + throw new BaseException( + { + code: 'AUTH_FAILED', + message: 'Доступ запрещен: требуется валидный токен авторизации', + details: this.getAuthDetails(err, info), + }, + HttpStatus.UNAUTHORIZED, + ); + } + + private isPublicOrHasToken(context: ExecutionContext): boolean { + const { query } = context + .switchToHttp() + .getRequest>(); + + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + + return !!(isPublic || query.token); + } + + private getAuthDetails(err: unknown, info: any) { + const message = info?.message || (err instanceof Error ? err.message : null); + + return message ? [{ target: 'auth', reason: message }] : []; + } +} diff --git a/src/shared/guards/cookie.guard.ts b/src/shared/guards/cookie.guard.ts new file mode 100644 index 0000000..89300ab --- /dev/null +++ b/src/shared/guards/cookie.guard.ts @@ -0,0 +1,27 @@ +import { HttpStatus, Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { BaseException } from '@shared/error'; +import type { JwtPayload } from '@shared/types'; + +@Injectable() +export class CookieAuthGuard extends AuthGuard('cookie') { + handleRequest(err: unknown, user: TUser, info: any): TUser { + if (err || !user) { + throw new BaseException( + { + code: 'INVALID_REFRESH_TOKEN', + message: 'Refresh токен невалиден или отсутствует', + details: [ + { + target: 'auth', + reason: info?.message || 'Token verification failed', + }, + ], + }, + HttpStatus.UNAUTHORIZED, + ); + } + + return user; + } +} diff --git a/src/shared/guards/index.ts b/src/shared/guards/index.ts new file mode 100644 index 0000000..20ada34 --- /dev/null +++ b/src/shared/guards/index.ts @@ -0,0 +1,2 @@ +export { BearerAuthGuard } from './bearer.guard'; +export { CookieAuthGuard } from './cookie.guard'; diff --git a/src/shared/migration/index.ts b/src/shared/migration/index.ts new file mode 100644 index 0000000..1be4fe2 --- /dev/null +++ b/src/shared/migration/index.ts @@ -0,0 +1 @@ +export { MigrationService } from './migration.service'; diff --git a/src/shared/migration/migration.service.ts b/src/shared/migration/migration.service.ts new file mode 100644 index 0000000..e1e49f9 --- /dev/null +++ b/src/shared/migration/migration.service.ts @@ -0,0 +1,27 @@ +import { Inject, Injectable, OnModuleInit, Logger } from '@nestjs/common'; +import { migrate } from 'drizzle-orm/node-postgres/migrator'; +import { DATABASE_SERVICE, type DatabaseService } from '@libs/database'; +import * as path from 'path'; + +@Injectable() +export class MigrationService implements OnModuleInit { + private readonly logger = new Logger(MigrationService.name); + + constructor( + @Inject(DATABASE_SERVICE) + private readonly db: DatabaseService>, + ) {} + + async onModuleInit() { + this.logger.debug('Checking for database migrations...'); + try { + await migrate(this.db, { + migrationsFolder: path.resolve(process.cwd(), 'migrations'), + }); + this.logger.debug('Migrations completed or already up to date'); + } catch (error) { + this.logger.error('Migration failed', error); + process.exit(1); + } + } +} diff --git a/src/shared/schemas/index.ts b/src/shared/schemas/index.ts new file mode 100644 index 0000000..b3c8aa4 --- /dev/null +++ b/src/shared/schemas/index.ts @@ -0,0 +1 @@ +export * from './pagination-response.schema'; diff --git a/src/shared/schemas/pagination-response.schema.ts b/src/shared/schemas/pagination-response.schema.ts new file mode 100644 index 0000000..0d3fcca --- /dev/null +++ b/src/shared/schemas/pagination-response.schema.ts @@ -0,0 +1,29 @@ +import { z } from 'zod/v4'; + +export const paginationResponseSchema = z.object({ + hasNextPage: z + .boolean() + .describe('Флаг наличия следующей страницы. True, если текущая страница не последняя.'), + hasPrevPage: z + .boolean() + .describe('Флаг наличия предыдущей страницы. True, если текущая страница больше первой.'), + total: z + .number() + .int() + .nonnegative() + .describe('Общее количество записей, соответствующих поисковому запросу/фильтрам.'), + totalPages: z + .number() + .int() + .nonnegative() + .describe('Общее количество страниц, рассчитанное на основе limit.'), + page: z.number().int().positive().describe('Номер текущей страницы (начиная с 1).'), + limit: z.number().int().positive().describe('Количество элементов на одну страницу.'), +}); + +export const createPaginationSchema = (itemSchema: T) => { + return z.object({ + data: z.array(itemSchema), + meta: paginationResponseSchema, + }); +}; diff --git a/src/shared/types/fastify.d.ts b/src/shared/types/fastify.d.ts new file mode 100644 index 0000000..9c77358 --- /dev/null +++ b/src/shared/types/fastify.d.ts @@ -0,0 +1,7 @@ +import type { JwtPayload } from './jwt-payload'; + +declare module 'fastify' { + interface FastifyRequest { + user?: JwtPayload; + } +} diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts new file mode 100644 index 0000000..9a3c79a --- /dev/null +++ b/src/shared/types/index.ts @@ -0,0 +1 @@ +export type { JwtPayload } from './jwt-payload'; diff --git a/src/shared/types/jwt-payload.ts b/src/shared/types/jwt-payload.ts new file mode 100644 index 0000000..c788698 --- /dev/null +++ b/src/shared/types/jwt-payload.ts @@ -0,0 +1,8 @@ +export interface JwtPayload { + sub: string; + email: string; + role: string; + iss: string; + aud: string; + jti: string; +} diff --git a/src/shared/workers/enum.ts b/src/shared/workers/enum.ts new file mode 100644 index 0000000..433d08a --- /dev/null +++ b/src/shared/workers/enum.ts @@ -0,0 +1,7 @@ +export enum Queues { + MAIL = 'MAIL_QUEUE', +} + +export enum MailJobs { + SEND_TEAM_INVITATION = 'SEND_TEAM_INVITATION', +} diff --git a/src/shared/workers/events/index.ts b/src/shared/workers/events/index.ts new file mode 100644 index 0000000..f0cfd4e --- /dev/null +++ b/src/shared/workers/events/index.ts @@ -0,0 +1 @@ +export { TeamInvitationEvent } from './team-invitation.event'; diff --git a/src/shared/workers/events/team-invitation.event.ts b/src/shared/workers/events/team-invitation.event.ts new file mode 100644 index 0000000..5dc9d67 --- /dev/null +++ b/src/shared/workers/events/team-invitation.event.ts @@ -0,0 +1,7 @@ +export class TeamInvitationEvent { + constructor( + public email: string, + public teamName: string, + public inviteUrl: string, + ) {} +} diff --git a/src/shared/workers/index.ts b/src/shared/workers/index.ts new file mode 100644 index 0000000..c14cbc2 --- /dev/null +++ b/src/shared/workers/index.ts @@ -0,0 +1,2 @@ +export { MailJobs, Queues } from './enum'; +export { MailProcessor } from './mail'; diff --git a/src/shared/workers/mail/index.ts b/src/shared/workers/mail/index.ts new file mode 100644 index 0000000..a059e2b --- /dev/null +++ b/src/shared/workers/mail/index.ts @@ -0,0 +1 @@ +export { MailProcessor } from './worker'; diff --git a/src/shared/workers/mail/worker.ts b/src/shared/workers/mail/worker.ts new file mode 100644 index 0000000..3fe4d34 --- /dev/null +++ b/src/shared/workers/mail/worker.ts @@ -0,0 +1,56 @@ +import { Processor, WorkerHost } from '@nestjs/bullmq'; +import { MailJobs, Queues } from '../enum'; +import type { Job } from 'bullmq'; +import { IMailPort } from '@shared/adapters/mail'; +import { Inject } from '@nestjs/common'; +import { TeamInvitationEvent } from '../events'; + +@Processor(Queues.MAIL) +export class MailProcessor extends WorkerHost { + constructor( + @Inject('IMailPort') + private readonly mailAdapter: IMailPort, + ) { + super(); + } + + async process(job: Job): Promise; + async process(job: Job): Promise { + await job.log(`[START] Job ID: ${job.id} | Type: ${job.name}`); + + try { + switch (job.name) { + case MailJobs.SEND_TEAM_INVITATION: + await this.sendTeamInvitation(job); + break; + default: + await job.log(`[WRN] No handler for job: ${job.name}`); + await job.updateProgress(100); + } + + await job.log(`[DONE] Job ${job.id} processed`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + const errorStack = error instanceof Error ? error.stack : ''; + + await job.log(`[FAIL] ${errorMessage}`); + if (errorStack) { + await job.log(errorStack); + } + + throw error; + } + } + + private sendTeamInvitation = async (job: Job) => { + const { email, teamName, inviteUrl } = job.data; + + await job.log(`Sending team(${teamName}) invitation link to: ${email}`); + await job.updateProgress(30); + + await this.mailAdapter.sendTeamInvitation(email, teamName, inviteUrl); + + await job.log(`Team invitation link delivered to ${email}`); + await job.updateProgress(100); + }; +} diff --git a/src/user/application/controller/index.ts b/src/user/application/controller/index.ts new file mode 100644 index 0000000..0ea2e27 --- /dev/null +++ b/src/user/application/controller/index.ts @@ -0,0 +1,2 @@ +export { UserController } from './user/controller'; +export { UserSettingsController } from './settings/controller'; diff --git a/src/user/application/controller/settings/controller.ts b/src/user/application/controller/settings/controller.ts new file mode 100644 index 0000000..66a174c --- /dev/null +++ b/src/user/application/controller/settings/controller.ts @@ -0,0 +1,16 @@ +import { Body, Patch } from '@nestjs/common'; +import { UserFacade } from '../../user.facade'; +import { PatchMeNotificationsSwagger } from './swagger'; +import { ApiBaseController, GetUserId } from '@shared/decorators'; +import { UpdateNotificationsDto } from '../../dtos'; + +@ApiBaseController('users/me', 'Account Settings', true) +export class UserSettingsController { + constructor(private readonly facade: UserFacade) {} + + @Patch('notifications') + @PatchMeNotificationsSwagger() + async updateNotifications(@Body() settings: UpdateNotificationsDto, @GetUserId() id: string) { + return this.facade.updateNotifications(id, settings); + } +} diff --git a/src/user/application/controller/settings/swagger.ts b/src/user/application/controller/settings/swagger.ts new file mode 100644 index 0000000..956284f --- /dev/null +++ b/src/user/application/controller/settings/swagger.ts @@ -0,0 +1,23 @@ +import { ApiBody, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { applyDecorators } from '@nestjs/common'; +import { ApiUnauthorized, ApiValidationError } from '@shared/error'; +import { ActionResponse } from '@shared/dtos'; +import { UpdateNotificationsDto } from '../../dtos'; + +export const PatchMeNotificationsSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Обновить настройки уведомлений', + description: 'Частичное обновление настроек email и push уведомлений.', + }), + ApiBody({ + type: UpdateNotificationsDto.Output, + }), + ApiResponse({ + status: 200, + description: 'Настройки успешно сохранены.', + type: ActionResponse.Output, + }), + ApiValidationError('Некорректный формат настроек'), + ApiUnauthorized(), + ); diff --git a/src/user/application/controller/user/controller.ts b/src/user/application/controller/user/controller.ts new file mode 100644 index 0000000..ec6fd3d --- /dev/null +++ b/src/user/application/controller/user/controller.ts @@ -0,0 +1,40 @@ +import { Body, Get, Patch, Post, Query } from '@nestjs/common'; +import { GetMeActivitySwagger, GetMeSwagger, PatchMeSwagger, PostMeAvatarSwagger } from './swagger'; +import { UpdateProfileDto } from '../../dtos'; +import { ApiBaseController, ExtractFastifyFile, GetUserId } from '@shared/decorators'; +import { UserFacade } from '../../user.facade'; +import { PaginationDto } from '@shared/dtos'; +import { FileUploadDto } from '@core/modules/media'; + +@ApiBaseController('users/me', 'Account Profile', true) +export class UserController { + constructor(private readonly facade: UserFacade) {} + + @Get() + @GetMeSwagger() + async getProfile(@GetUserId() id: string) { + return this.facade.getProfile(id); + } + + @Patch() + @PatchMeSwagger() + async updateProfile(@Body() dto: UpdateProfileDto, @GetUserId() id: string) { + return this.facade.updateProfile(id, dto); + } + + @Get('activity') + @GetMeActivitySwagger() + async getActivity(@Query() query: PaginationDto, @GetUserId() id: string) { + return this.facade.getActivity(id, query.page, query.limit); + } + + @Post('avatar') + @PostMeAvatarSwagger() + async uploadAvatar( + @ExtractFastifyFile() fileDto: FileUploadDto, + @GetUserId() + userId: string, + ) { + return this.facade.uploadAvatar(userId, fileDto); + } +} diff --git a/src/user/application/controller/user/swagger.ts b/src/user/application/controller/user/swagger.ts new file mode 100644 index 0000000..568fb14 --- /dev/null +++ b/src/user/application/controller/user/swagger.ts @@ -0,0 +1,116 @@ +import { + ApiBody, + ApiConsumes, + ApiExtraModels, + ApiOperation, + ApiQuery, + ApiResponse, +} from '@nestjs/swagger'; +import { UpdateProfileDto, UserResponse } from '../../dtos'; +import { applyDecorators } from '@nestjs/common'; +import { ApiBadRequest, ApiUnauthorized, ApiValidationError } from '@shared/error'; +import { ActionResponse } from '@shared/dtos'; + +export const GetMeSwagger = () => + applyDecorators( + ApiExtraModels(UserResponse.Output), + ApiOperation({ + summary: 'Получить профиль текущего пользователя', + description: + 'Возвращает полную структуру профиля, включая вложенные объекты безопасности и настроек.', + }), + ApiResponse({ + status: 200, + description: 'Данные профиля успешно получены.', + type: UserResponse.Output, + }), + ApiUnauthorized(), + ); + +export const PatchMeSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Обновить данные профиля', + description: 'Позволяет точечно обновить имя, bio, часовой пояс и язык интерфейса.', + }), + ApiBody({ type: UpdateProfileDto.Output }), + ApiResponse({ + status: 200, + description: 'Профиль успешно обновлен.', + type: ActionResponse.Output, + }), + ApiValidationError('Ошибка валидации (например, слишком короткое имя)', [ + { + field: 'fullName', + message: 'Строка должна содержать минимум 2 символа', + code: 'too_small', + }, + ]), + ApiUnauthorized(), + ); + +export const GetMeActivitySwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Получить ленту активности пользователя', + description: 'Возвращает список последних действий пользователя (логи).', + }), + ApiQuery({ + name: 'limit', + required: false, + type: String, + description: 'Количество записей для вывода (по умолчанию 10)', + example: '15', + }), + ApiResponse({ + status: 200, + description: 'Список активностей успешно получен.', + schema: { + example: { + data: [ + { + id: 'clj1abc230000jk78', + eventType: 'TASK_COMPLETED', + description: 'Завершена задача "Обновить текст лендинга"', + createdAt: '2026-04-10T20:00:00.000Z', + metadata: { taskId: 'clj1xyz990000abc1' }, + }, + ], + meta: { + total: 45, + page: 1, + limit: 20, + totalPages: 3, + }, + }, + }, + }), + ApiUnauthorized(), + ); + +export const PostMeAvatarSwagger = () => + applyDecorators( + ApiOperation({ + summary: 'Загрузить новую аватарку', + description: 'Загрузка файла изображения для профиля пользователя.', + }), + ApiConsumes('multipart/form-data'), + ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }), + ApiResponse({ + status: 201, + description: 'Аватар успешно загружен.', + type: ActionResponse.Output, + }), + ApiBadRequest('Файл не передан или имеет неверный формат'), + ApiUnauthorized(), + ); diff --git a/src/user/application/dtos/index.ts b/src/user/application/dtos/index.ts new file mode 100644 index 0000000..bdcec8b --- /dev/null +++ b/src/user/application/dtos/index.ts @@ -0,0 +1 @@ +export { UpdateProfileDto, UpdateNotificationsDto, UserResponse } from './user.dto'; diff --git a/src/user/application/dtos/user.dto.ts b/src/user/application/dtos/user.dto.ts new file mode 100644 index 0000000..de3ffe4 --- /dev/null +++ b/src/user/application/dtos/user.dto.ts @@ -0,0 +1,82 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod/v4'; + +const NotificationsSchema = z + .object({ + email: z.object({ + task_assigned: z.boolean().describe('Уведомление на почту при назначении задачи'), + mentions: z.boolean().describe('Уведомление на почту при упоминании в комментариях'), + daily_summary: z.boolean().describe('Ежедневная сводка задач на почту'), + }), + push: z.object({ + task_assigned: z.boolean().describe('Push-уведомление при назначении задачи'), + reminders: z.boolean().describe('Push-уведомления о дедлайнах'), + }), + }) + .describe('Настройки уведомлений пользователя'); + +export const UpdateNotificationsSchema = NotificationsSchema.partial() + .refine((data) => Object.keys(data).length > 0, { + error: 'Необходимо передать хотя бы одно поле для обновления', + abort: true, + }) + .describe('Схема для частичного обновления настроек уведомлений'); + +export class UpdateNotificationsDto extends createZodDto(UpdateNotificationsSchema) {} + +const SecuritySchema = z + .object({ + is2faEnabled: z.boolean().describe('Статус двухфакторной аутентификации'), + lastPasswordChange: z.string().datetime().describe('Дата последнего изменения пароля'), + }) + .describe('Данные безопасности аккаунта'); + +const ProfileSchema = z.object({ + firstName: z.string().describe('Имя пользователя'), + lastName: z.string().describe('Фамилия'), + middleName: z.string().nullable().describe('Отчество'), + bio: z.string().nullable().describe('О себе'), + avatarUrl: z.string().url().nullable().describe('Ссылка на аватар в S3'), + timezone: z.string().describe('Временная зона'), + language: z.string().describe('Язык интерфейса'), + createdAt: z.string().datetime().describe('Дата регистрации'), + updatedAt: z.string().datetime().describe('Дата последнего обновления профиля'), +}); + +export const UserSchema = z.object({ + id: z.string().describe('Уникальный идентификатор (CUID/UUID)'), + email: z.string().email().describe('Электронная почта'), + profile: ProfileSchema, + security: SecuritySchema, + notifications: NotificationsSchema, +}); + +export class UserResponse extends createZodDto(UserSchema) {} + +export const UpdateProfileSchema = z + .object({ + firstName: z + .string() + .min(1, 'Имя не может быть пустым') + .max(50, 'Имя слишком длинное') + .optional(), + lastName: z + .string() + .min(1, 'Фамилия не может быть пустой') + .max(50, 'Фамилия слишком длинная') + .optional(), + middleName: z.string().max(50, 'Отчество слишком длинное').nullish(), + bio: z.string().max(1000, 'О себе не более 1000 символов').nullish(), + timezone: z.string().max(50).optional(), + language: z + .string() + .length(2, 'Используйте формат ISO (например, "ru" или "en")') + .optional(), + }) + .refine((data) => Object.keys(data).length > 0, { + error: 'Необходимо передать хотя бы одно поле для обновления', + abort: true, + }) + .describe('Схема для частичного обновления данных профиля'); + +export class UpdateProfileDto extends createZodDto(UpdateProfileSchema) {} diff --git a/src/user/application/use-cases/find-profile.query.ts b/src/user/application/use-cases/find-profile.query.ts new file mode 100644 index 0000000..df2972c --- /dev/null +++ b/src/user/application/use-cases/find-profile.query.ts @@ -0,0 +1,25 @@ +import { IUserRepository } from '@core/user/domain/repository'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class FindProfileQuery { + constructor( + @Inject('IUserRepository') + private readonly userRepo: IUserRepository, + ) {} + + async execute(userId: string) { + const { user, notifications, security } = await this.userRepo.findProfile(userId); + + if (!user) { + throw new BaseException( + { code: 'USER_NOT_FOUND', message: 'Пользователь не найден' }, + HttpStatus.NOT_FOUND, + ); + } + + const { id, email, ...profile } = user; + return { id, email, profile, security, notifications }; + } +} diff --git a/src/user/application/use-cases/find-user.query.ts b/src/user/application/use-cases/find-user.query.ts new file mode 100644 index 0000000..a83ec65 --- /dev/null +++ b/src/user/application/use-cases/find-user.query.ts @@ -0,0 +1,24 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { IUserRepository } from '@core/user/domain/repository'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class FindUserQuery { + constructor( + @Inject('IUserRepository') + private readonly repository: IUserRepository, + ) {} + + async execute(params: { email?: string; id?: string }) { + if (params.email) return this.repository.findByEmail(params.email); + if (params.id) return this.repository.findById(params.id); + + throw new BaseException( + { + code: 'QUERY_PARAMS_MISSING', + message: 'Не указаны параметры поиска', + }, + HttpStatus.BAD_REQUEST, + ); + } +} diff --git a/src/user/application/use-cases/get-activity.query.ts b/src/user/application/use-cases/get-activity.query.ts new file mode 100644 index 0000000..5921bd6 --- /dev/null +++ b/src/user/application/use-cases/get-activity.query.ts @@ -0,0 +1,30 @@ +import { IUserRepository } from '@core/user/domain/repository'; +import { Inject, Injectable } from '@nestjs/common'; + +@Injectable() +export class GetActivityQuery { + constructor( + @Inject('IUserRepository') + private readonly userRepo: IUserRepository, + ) {} + + async execute(id: string, page: number, limit: number) { + const safeLimit = Math.min(limit, 50); + const offset = (page - 1) * safeLimit; + + const { items, total } = await this.userRepo.findActivityByUser(id, { + limit: safeLimit, + offset, + }); + + return { + items, + meta: { + total, + page, + limit: safeLimit, + totalPages: Math.ceil(total / safeLimit), + }, + }; + } +} diff --git a/src/user/application/use-cases/index.ts b/src/user/application/use-cases/index.ts new file mode 100644 index 0000000..fa08830 --- /dev/null +++ b/src/user/application/use-cases/index.ts @@ -0,0 +1,8 @@ +export { FindProfileQuery } from './find-profile.query'; +export { FindUserQuery } from './find-user.query'; +export { GetActivityQuery } from './get-activity.query'; +export { RegisterUserUseCase } from './register-user.use-case'; +export { UpdateNotificationsUseCase } from './update-notifications.use-case'; +export { UpdatePasswordUseCase } from './update-password.use-case'; +export { UpdateProfileUseCase } from './update-profile.use-case'; +export { UploadAvatarUseCase } from './upload-avatar.use-case'; diff --git a/src/user/application/use-cases/register-user.use-case.ts b/src/user/application/use-cases/register-user.use-case.ts new file mode 100644 index 0000000..7e150f8 --- /dev/null +++ b/src/user/application/use-cases/register-user.use-case.ts @@ -0,0 +1,52 @@ +import type { NewUser } from '@core/user/domain/entities'; +import { IUserRepository } from '@core/user/domain/repository'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { createId } from '@paralleldrive/cuid2'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class RegisterUserUseCase { + constructor( + @Inject('IUserRepository') + private readonly repository: IUserRepository, + ) {} + + async execute(dto: NewUser & { password: string }) { + const existingUser = await this.repository.findByEmail(dto.email); + + if (existingUser?.user) { + throw new BaseException( + { + code: 'USER_ALREADY_EXISTS', + message: `Пользователь с email ${dto.email} уже зарегистрирован`, + details: [{ target: 'email', value: dto.email }], + }, + HttpStatus.CONFLICT, + ); + } + + try { + const user = await this.repository.create(dto); + + await Promise.all([ + this.repository.logActivity({ + eventType: 'registered', + userId: user.id, + id: createId(), + }), + this.repository.updatePasswordHash(user.id, dto.password), + ]); + + return user; + } catch (error) { + throw new BaseException( + { + code: 'USER_REGISTRATION_FAILED', + message: 'Не удалось завершить регистрацию', + details: [{ reason: error instanceof Error ? error.message : 'DB error' }], + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/user/application/use-cases/update-notifications.use-case.ts b/src/user/application/use-cases/update-notifications.use-case.ts new file mode 100644 index 0000000..022e251 --- /dev/null +++ b/src/user/application/use-cases/update-notifications.use-case.ts @@ -0,0 +1,67 @@ +import { IUserRepository } from '@core/user/domain/repository'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { BaseException } from '@shared/error'; +import { UpdateNotificationsDto } from '../dtos'; +import { createId } from '@paralleldrive/cuid2'; + +@Injectable() +export class UpdateNotificationsUseCase { + constructor( + @Inject('IUserRepository') + private readonly userRepo: IUserRepository, + ) {} + + async execute(id: string, dto: UpdateNotificationsDto) { + const user = await this.userRepo.findById(id); + + if (!user) { + throw new BaseException( + { code: 'USER_NOT_FOUND', message: 'Пользователь не найден' }, + HttpStatus.NOT_FOUND, + ); + } + + try { + const isUpdated = await this.userRepo.updateNotifications(id, { + email: dto.email, + push: dto.push, + }); + + if (!isUpdated) { + throw new BaseException( + { + code: 'NOTIFICATIONS_UPDATE_FAILED', + message: 'Не удалось обновить настройки уведомлений', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + await this.userRepo.logActivity({ + id: createId(), + userId: id, + eventType: 'NOTIFICATIONS_UPDATED', + }); + + return { + success: true, + message: 'Настройки уведомлений обновлены', + }; + } catch (error) { + if (error instanceof BaseException) throw error; + + throw new BaseException( + { + code: 'USER_SETTINGS_ERROR', + message: 'Ошибка при сохранении настроек пользователя', + details: [ + { + reason: error instanceof Error ? error.message : 'Database error', + }, + ], + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/user/application/use-cases/update-password.use-case.ts b/src/user/application/use-cases/update-password.use-case.ts new file mode 100644 index 0000000..c718ae3 --- /dev/null +++ b/src/user/application/use-cases/update-password.use-case.ts @@ -0,0 +1,50 @@ +import { IUserRepository } from '@core/user/domain/repository'; +import { Injectable, Inject, HttpStatus } from '@nestjs/common'; +import { BaseException } from '@shared/error'; + +@Injectable() +export class UpdatePasswordUseCase { + constructor( + @Inject('IUserRepository') + private readonly repository: IUserRepository, + ) {} + + async execute(email: string, password: string) { + const result = await this.repository.findByEmail(email); + + if (!result?.user) { + throw new BaseException( + { + code: 'USER_NOT_FOUND', + message: 'Пользователь для обновления пароля не найден', + }, + HttpStatus.NOT_FOUND, + ); + } + + try { + const isUpdated = await this.repository.updatePasswordHash(result.user.id, password); + + if (!isUpdated) { + throw new BaseException( + { + code: 'PASSWORD_UPDATE_FAILED', + message: 'Запись не была изменена', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + return isUpdated; + } catch (error) { + throw new BaseException( + { + code: 'DATABASE_ERROR', + message: 'Ошибка при работе с БД', + details: [{ reason: error instanceof Error ? error.message : 'Unknown' }], + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/user/application/use-cases/update-profile.use-case.ts b/src/user/application/use-cases/update-profile.use-case.ts new file mode 100644 index 0000000..4a43377 --- /dev/null +++ b/src/user/application/use-cases/update-profile.use-case.ts @@ -0,0 +1,32 @@ +import { IUserRepository } from '@core/user/domain/repository'; +import { Injectable, Inject, HttpStatus } from '@nestjs/common'; +import { UpdateProfileDto } from '../dtos'; +import { BaseException } from '@shared/error'; +import { createId } from '@paralleldrive/cuid2'; + +@Injectable() +export class UpdateProfileUseCase { + constructor( + @Inject('IUserRepository') + private readonly userRepo: IUserRepository, + ) {} + + async execute(id: string, dto: UpdateProfileDto) { + const isUpdated = await this.userRepo.updateProfile(id, dto); + + if (!isUpdated) { + throw new BaseException( + { code: 'PROFILE_UPDATE_FAILED', message: 'Не удалось обновить данные' }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + await this.userRepo.logActivity({ + id: createId(), + userId: id, + eventType: 'PROFILE_UPDATED', + }); + + return { success: true }; + } +} diff --git a/src/user/application/use-cases/upload-avatar.use-case.ts b/src/user/application/use-cases/upload-avatar.use-case.ts new file mode 100644 index 0000000..12da3d0 --- /dev/null +++ b/src/user/application/use-cases/upload-avatar.use-case.ts @@ -0,0 +1,29 @@ +import { FileUploadDto, IUserMedia, USER_MEDIA_TOKEN } from '@core/modules/media'; +import { IUserRepository } from '@core/user/domain/repository'; +import { Inject, Injectable } from '@nestjs/common'; +import { createId } from '@paralleldrive/cuid2'; + +@Injectable() +export class UploadAvatarUseCase { + constructor( + @Inject('IUserRepository') + private readonly userRepo: IUserRepository, + @Inject(USER_MEDIA_TOKEN) + private readonly mediaService: IUserMedia, + ) {} + + async execute(userId: string, fileDto: FileUploadDto) { + const { url } = await this.mediaService.uploadUserAvatar(userId, fileDto, (url) => + this.userRepo.updateAvatar(userId, url), + ); + + await this.userRepo.logActivity({ + id: createId(), + userId, + eventType: 'AVATAR_CHANGED', + metadata: { url }, + }); + + return { success: true, url }; + } +} diff --git a/src/user/application/user.facade.ts b/src/user/application/user.facade.ts new file mode 100644 index 0000000..e49cd8f --- /dev/null +++ b/src/user/application/user.facade.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; +import { + FindProfileQuery, + GetActivityQuery, + UpdateNotificationsUseCase, + UpdateProfileUseCase, + UploadAvatarUseCase, +} from './use-cases'; +import { UpdateProfileDto, UpdateNotificationsDto } from './dtos'; +import { FileUploadDto } from '@core/modules/media'; + +@Injectable() +export class UserFacade { + constructor( + private readonly findProfileQuery: FindProfileQuery, + private readonly getActivityQuery: GetActivityQuery, + private readonly updateNotificationsUC: UpdateNotificationsUseCase, + private readonly updateProfileUC: UpdateProfileUseCase, + private readonly uploadAvatarUC: UploadAvatarUseCase, + ) {} + + public async getProfile(userId: string) { + return this.findProfileQuery.execute(userId); + } + + public async getActivity(userId: string, page: number, limit: number) { + return this.getActivityQuery.execute(userId, page, limit); + } + + public async updateProfile(userId: string, dto: UpdateProfileDto) { + return this.updateProfileUC.execute(userId, dto); + } + + public async updateNotifications(userId: string, dto: UpdateNotificationsDto) { + return this.updateNotificationsUC.execute(userId, dto); + } + + public async uploadAvatar(userId: string, file: FileUploadDto) { + return this.uploadAvatarUC.execute(userId, file); + } +} diff --git a/src/user/domain/entities/index.ts b/src/user/domain/entities/index.ts new file mode 100644 index 0000000..54f31af --- /dev/null +++ b/src/user/domain/entities/index.ts @@ -0,0 +1 @@ +export type * from './user.domain'; diff --git a/src/user/domain/entities/user.domain.ts b/src/user/domain/entities/user.domain.ts new file mode 100644 index 0000000..2bf467e --- /dev/null +++ b/src/user/domain/entities/user.domain.ts @@ -0,0 +1,30 @@ +import { InferSelectModel, InferInsertModel } from 'drizzle-orm'; +import { + users, + userSecurity, + userNotifications, + userActivity, +} from '../../infrastructure/persistence/models/user.entity'; + +export type User = InferSelectModel; +export type NewUser = InferInsertModel; + +export type UserSecurity = InferSelectModel; +export type NewUserSecurity = InferInsertModel; + +export type UserNotifications = InferSelectModel; +export type NotificationSettings = Pick; + +export type UserActivity = InferSelectModel; +export type NewUserActivity = InferInsertModel; + +export type UserProfile = { + user: User; + security: Pick; + notifications: NotificationSettings['settings']; +}; + +export type UserWithSecurity = { + user: User; + security: Pick; +}; diff --git a/src/user/domain/repository/index.ts b/src/user/domain/repository/index.ts new file mode 100644 index 0000000..a9419f8 --- /dev/null +++ b/src/user/domain/repository/index.ts @@ -0,0 +1 @@ +export { IUserRepository } from './user.repository.interface'; diff --git a/src/user/domain/repository/user.repository.interface.ts b/src/user/domain/repository/user.repository.interface.ts new file mode 100644 index 0000000..e2c4bbe --- /dev/null +++ b/src/user/domain/repository/user.repository.interface.ts @@ -0,0 +1,28 @@ +import type { + NewUser, + NewUserActivity, + User, + UserActivity, + UserNotifications, + UserProfile, + UserWithSecurity, +} from '../entities/user.domain'; + +export interface IUserRepository { + create(data: NewUser): Promise; + findById(id: string): Promise; + findByEmail(email: string): Promise; + findProfile(id: string): Promise; + findActivityByUser( + userId: string, + options: { limit: number; offset: number }, + ): Promise<{ + items: UserActivity[]; + total: number; + }>; + updateAvatar(id: string, url: string): Promise; + updateProfile(id: string, data: Partial): Promise; + updatePasswordHash(id: string, hash: string): Promise; + updateNotifications(id: string, settings: UserNotifications['settings']): Promise; + logActivity(data: NewUserActivity): Promise; +} diff --git a/src/user/index.ts b/src/user/index.ts new file mode 100644 index 0000000..4e472d9 --- /dev/null +++ b/src/user/index.ts @@ -0,0 +1,3 @@ +export { UserModule } from './user.module'; +export { RegisterUserUseCase, FindUserQuery, UpdatePasswordUseCase } from './application/use-cases'; +export { User } from './domain/entities/user.domain'; diff --git a/src/user/infrastructure/persistence/models/index.ts b/src/user/infrastructure/persistence/models/index.ts new file mode 100644 index 0000000..426faed --- /dev/null +++ b/src/user/infrastructure/persistence/models/index.ts @@ -0,0 +1 @@ +export { userActivity, userNotifications, userSecurity, users } from './user.entity'; diff --git a/src/user/infrastructure/persistence/models/user.entity.ts b/src/user/infrastructure/persistence/models/user.entity.ts new file mode 100644 index 0000000..4078a21 --- /dev/null +++ b/src/user/infrastructure/persistence/models/user.entity.ts @@ -0,0 +1,59 @@ +import { createId } from '@paralleldrive/cuid2'; +import { varchar, text, timestamp, boolean, jsonb } from 'drizzle-orm/pg-core'; +import { baseSchema } from '@shared/entities'; + +export const users = baseSchema.table('users', { + id: text('id') + .primaryKey() + .$defaultFn(() => createId()), + + firstName: varchar('first_name', { length: 50 }).notNull(), + lastName: varchar('last_name', { length: 50 }).notNull(), + middleName: varchar('middle_name', { length: 50 }), + email: varchar('email', { length: 255 }).notNull().unique(), + bio: text('bio'), + avatarUrl: varchar('avatar_url', { length: 512 }), + timezone: varchar('timezone', { length: 50 }).default('UTC').notNull(), + language: varchar('language', { length: 5 }).default('ru').notNull(), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), +}); + +export const userSecurity = baseSchema.table('user_security', { + userId: text('user_id') + .primaryKey() + .references(() => users.id, { onDelete: 'cascade' }), + passwordHash: varchar('password_hash', { length: 255 }).notNull(), + is2faEnabled: boolean('is_2fa_enabled').default(false).notNull(), + twoFactorSecret: text('two_factor_secret'), + lastPasswordChange: timestamp('last_password_change', { withTimezone: true }) + .defaultNow() + .notNull(), +}); + +export const userNotifications = baseSchema.table('user_notifications', { + userId: text('user_id') + .primaryKey() + .references(() => users.id, { onDelete: 'cascade' }), + settings: jsonb('settings') + .$type<{ + email: { task_assigned: boolean; mentions: boolean; daily_summary: boolean }; + push: { task_assigned: boolean; reminders: boolean }; + }>() + .default({ + email: { task_assigned: true, mentions: true, daily_summary: false }, + push: { task_assigned: true, reminders: true }, + }) + .notNull(), +}); + +export const userActivity = baseSchema.table('user_activity', { + id: text('id').primaryKey(), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), + eventType: varchar('event_type', { length: 50 }).notNull(), + entityId: varchar('entity_id'), + metadata: jsonb('metadata'), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), +}); diff --git a/src/user/infrastructure/persistence/repositories/index.ts b/src/user/infrastructure/persistence/repositories/index.ts new file mode 100644 index 0000000..c9c59cf --- /dev/null +++ b/src/user/infrastructure/persistence/repositories/index.ts @@ -0,0 +1 @@ +export { UserRepository } from './user.repository'; diff --git a/src/user/infrastructure/persistence/repositories/user.repository.ts b/src/user/infrastructure/persistence/repositories/user.repository.ts new file mode 100644 index 0000000..b036891 --- /dev/null +++ b/src/user/infrastructure/persistence/repositories/user.repository.ts @@ -0,0 +1,142 @@ +import { IUserRepository } from '@core/user/domain/repository'; +import * as sc from '../models'; +import { DATABASE_SERVICE, DatabaseService } from '@libs/database'; +import { Inject, Injectable } from '@nestjs/common'; +import { createId } from '@paralleldrive/cuid2'; +import { desc, eq, count } from 'drizzle-orm'; +import type { NewUser, NewUserActivity, User, UserNotifications } from '@core/user/domain/entities'; + +@Injectable() +export class UserRepository implements IUserRepository { + constructor( + @Inject(DATABASE_SERVICE) + private readonly db: DatabaseService, + ) {} + + private get fullUserQuery() { + return this.db + .select() + .from(sc.users) + .leftJoin(sc.userSecurity, eq(sc.users.id, sc.userSecurity.userId)) + .leftJoin(sc.userNotifications, eq(sc.users.id, sc.userNotifications.userId)); + } + + async findProfile(id: string) { + const [rows] = await this.fullUserQuery.where(eq(sc.users.id, id)); + if (!rows.users) return null; + const { lastPasswordChange, is2faEnabled } = rows.user_security; + const { settings } = rows.user_notifications; + + return { + user: rows.users, + security: { lastPasswordChange, is2faEnabled }, + notifications: settings, + }; + } + + async findById(id: string) { + const [row] = await this.fullUserQuery.where(eq(sc.users.id, id)); + if (!row || !row.user_security) return null; + return { + user: row.users, + security: { + passwordHash: row.user_security.passwordHash, + }, + }; + } + + async findByEmail(email: string) { + const [row] = await this.fullUserQuery.where(eq(sc.users.email, email.toLowerCase())); + if (!row || !row.user_security) return null; + return { + user: row.users, + security: { + passwordHash: row.user_security.passwordHash, + }, + }; + } + + async findSecurityByUserId(userId: string) { + const [result] = await this.db + .select() + .from(sc.userSecurity) + .where(eq(sc.userSecurity.userId, userId)); + return result || null; + } + + async create(data: NewUser) { + return await this.db.transaction(async (tx) => { + const [newUser] = await tx.insert(sc.users).values(data).returning(); + + await tx.insert(sc.userNotifications).values({ + userId: newUser.id, + }); + + return newUser; + }); + } + + async updateProfile(id: string, data: Partial) { + const { rowCount } = await this.db + .update(sc.users) + .set({ ...data, updatedAt: new Date() }) + .where(eq(sc.users.id, id)); + return (rowCount ?? 0) > 0; + } + + async updateNotifications(id: string, settings: UserNotifications['settings']) { + const { rowCount } = await this.db + .update(sc.userNotifications) + .set({ settings }) + .where(eq(sc.userNotifications.userId, id)); + return (rowCount ?? 0) > 0; + } + + async updateAvatar(id: string, url: string) { + const { rowCount } = await this.db + .update(sc.users) + .set({ avatarUrl: url, updatedAt: new Date() }) + .where(eq(sc.users.id, id)); + return (rowCount ?? 0) > 0; + } + + async updatePasswordHash(id: string, hash: string) { + const { rowCount } = await this.db + .insert(sc.userSecurity) + .values({ userId: id, passwordHash: hash }) + .onConflictDoUpdate({ + target: sc.userSecurity.userId, + set: { passwordHash: hash, lastPasswordChange: new Date() }, + }); + return (rowCount ?? 0) > 0; + } + + async logActivity(data: NewUserActivity) { + const { rowCount } = await this.db.insert(sc.userActivity).values({ + ...data, + id: data.id ?? createId(), + }); + return (rowCount ?? 0) > 0; + } + + async findActivityByUser(userId: string, options: { limit: number; offset: number }) { + const [totalResult, items] = await Promise.all([ + this.db + .select({ value: count() }) + .from(sc.userActivity) + .where(eq(sc.userActivity.userId, userId)), + this.db + .select() + .from(sc.userActivity) + .where(eq(sc.userActivity.userId, userId)) + .limit(options.limit) + .offset(options.offset) + .orderBy(desc(sc.userActivity.createdAt)), + ]); + + return { + items, + total: Number(totalResult[0]?.value ?? 0), + }; + } +} diff --git a/src/user/user.module.ts b/src/user/user.module.ts new file mode 100644 index 0000000..df7d473 --- /dev/null +++ b/src/user/user.module.ts @@ -0,0 +1,38 @@ +import { Module } from '@nestjs/common'; +import { MediaModule } from '@core/modules/media'; +import { UserRepository } from './infrastructure/persistence/repositories'; +import { UserController, UserSettingsController } from './application/controller'; +import { UserFacade } from './application/user.facade'; +import { + FindProfileQuery, + FindUserQuery, + GetActivityQuery, + RegisterUserUseCase, + UpdateNotificationsUseCase, + UpdatePasswordUseCase, + UpdateProfileUseCase, + UploadAvatarUseCase, +} from './application/use-cases'; + +const REPOSITORY = { + provide: 'IUserRepository', + useClass: UserRepository, +}; + +const USE_CASES = [ + UploadAvatarUseCase, + UpdateProfileUseCase, + UpdateNotificationsUseCase, + FindProfileQuery, + GetActivityQuery, +]; + +const EXTERNAL_USE_CASES = [RegisterUserUseCase, UpdatePasswordUseCase, FindUserQuery]; + +@Module({ + imports: [MediaModule], + controllers: [UserController, UserSettingsController], + providers: [...USE_CASES, ...EXTERNAL_USE_CASES, REPOSITORY, UserFacade], + exports: [...EXTERNAL_USE_CASES], +}) +export class UserModule {} diff --git a/templates/confirmation.hbs b/templates/confirmation.hbs new file mode 100644 index 0000000..a8cf39d --- /dev/null +++ b/templates/confirmation.hbs @@ -0,0 +1,91 @@ + + + + + + +
+
+

Task Tracker

+
+
+

Проверка безопасности

+

Привет, {{name}}! Используйте этот код для подтверждения:

+ +
{{#each codeArray}}{{this}}{{/each}}
+ +

Код будет активен в течение 15 минут.

+
+ +
+ + \ No newline at end of file diff --git a/templates/reset-password.hbs b/templates/reset-password.hbs new file mode 100644 index 0000000..735b91c --- /dev/null +++ b/templates/reset-password.hbs @@ -0,0 +1,92 @@ + + + + + + +
+
+

Task Tracker

+
+
+

Сброс пароля

+

Здравствуйте!

+

Мы получили запрос на восстановление пароля для вашего аккаунта.
Ваш + одноразовый код для сброса:

+
{{#each codeArray}}
{{this}}
{{/each}}
+ +

Никому не сообщайте этот код. Если вы не + запрашивали сброс пароля, немедленно обратитесь в поддержку.

+
+ +
+ + \ No newline at end of file diff --git a/templates/team-invitation.hbs b/templates/team-invitation.hbs new file mode 100644 index 0000000..9ed932b --- /dev/null +++ b/templates/team-invitation.hbs @@ -0,0 +1,94 @@ + + + + + + +
+
+

Task Tracker

+
+
+

Приглашение в команду

+

Вас пригласили присоединиться к команде {{teamName}}!

+ Присоединиться к команде +

+ Если кнопка не работает, скопируйте и вставьте эту ссылку в браузер:
+ {{inviteUrl}} +

+
+ +
+ + \ No newline at end of file diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index 50cda62..cc08a04 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -1,24 +1,32 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; +import { AppModule } from '../src/modules/app/app.module'; +import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify'; -describe('AppController (e2e)', () => { - let app: INestApplication; +describe('App (e2e)', () => { + let app: NestFastifyApplication; - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - }); + app = moduleFixture.createNestApplication(new FastifyAdapter()); - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); - }); + await app.init(); + await app.getHttpAdapter().getInstance().ready(); + }); + + afterEach(async () => { + await app.close(); + }); + + it('/health (GET)', async () => { + const res = await app.inject({ + method: 'GET', + url: '/health', + }); + + expect(res.statusCode).toBe(200); + expect(res.payload).toBe('healthy'); + }); }); diff --git a/test/jest-e2e.json b/test/jest-e2e.json deleted file mode 100644 index e9d912f..0000000 --- a/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} diff --git a/tsconfig.build.json b/tsconfig.build.json index 64f86c6..aed3485 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,4 @@ { - "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 95f5641..f447285 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,44 @@ { - "compilerOptions": { - "module": "commonjs", - "declaration": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "ES2021", - "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", - "incremental": true, - "skipLibCheck": true, - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false - } + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "types": ["node", "vitest/globals"], + "paths": { + "@libs/bootstrap": ["./libs/bootstrap/src"], + "@libs/bootstrap/*": ["./libs/bootstrap/src/*"], + "@libs/config": ["./libs/config/src"], + "@libs/config/*": ["./libs/config/src/*"], + "@libs/database": ["./libs/database/src"], + "@libs/database/*": ["./libs/database/src/*"], + "@libs/health": ["./libs/health/src"], + "@libs/health/*": ["./libs/health/src/*"], + "@libs/s3": ["./libs/s3/src"], + "@libs/s3/*": ["./libs/s3/src/*"], + "@shared/*": ["./src/shared/*"], + "@core/*": ["./src/*"] + } + }, + "include": [ + "src/**/*", + "libs/**/*", + "test/**/*", + "drizzle.config.ts", + "vitest.config.ts", + "vitest.config.e2e.ts" + ], + "exclude": ["dist", "node_modules"] } diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts new file mode 100644 index 0000000..494b16e --- /dev/null +++ b/vitest.config.e2e.ts @@ -0,0 +1,14 @@ +import { mergeConfig, defineConfig } from 'vitest/config'; +import baseConfig from './vitest.config'; + +export default mergeConfig( + baseConfig, + defineConfig({ + test: { + include: ['test/**/*.e2e-spec.ts'], + exclude: [], + pool: 'forks', + isolate: true, + }, + }), +); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..6c522f3 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,35 @@ +import path from 'path'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + root: './', + globals: true, + environment: 'node', + include: ['**/*.spec.ts'], + exclude: [ + '**/node_modules/**', + '**/dist/**', + '**/cypress/**', + '**/.{idea,git,cache,output,temp}/**', + '**/infra/**', + ], + alias: { + '@core': path.resolve(__dirname, './src'), + '@shared': path.resolve(__dirname, './src/shared'), + '@libs/bootstrap': path.join(process.cwd(), 'libs/bootstrap/src'), + '@libs/config': path.join(process.cwd(), 'libs/config/src'), + '@libs/database': path.join(process.cwd(), 'libs/database/src'), + '@libs/health': path.join(process.cwd(), 'libs/health/src'), + '@libs/s3': path.join(process.cwd(), 'libs/s3/src'), + }, + typecheck: { + enabled: true, + }, + }, + resolve: { + alias: { + src: path.resolve(__dirname, './src'), + }, + }, +});