diff --git a/.dockerignore b/.dockerignore index 324fcd27e..b86139a28 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,24 @@ -**/dist -**/node_modules -**/build -**/.cache \ No newline at end of file +# Local dev files +node_modules +dist +.env* +.git +.gitignore +.vscode +.idea + +# Tests +backend/tests +!backend/tests/actions/file-hash +packages/*/tests +**/*.test.ts +**/*.spec.ts +junit.xml +coverage + +# Backend specific +backend/tsconfig.tsbuildinfo +backend/npm-debug.log* +backend/yarn-debug.log* +backend/yarn-error.log* +backend/pnpm-debug.log* diff --git a/.env b/.env index f055f135b..a9f097acb 100644 --- a/.env +++ b/.env @@ -1,8 +1,8 @@ SERVER_PORT=3000 -QUASAR_ENDPOINT=http://localhost:3000 FRONTEND_URL=http://localhost:8003 DOCS_URL=http://localhost:4000 +BACKEND_URL=http://localhost:3000 MINIO_USER=minioadmin MINIO_PASSWORD=minioadmin @@ -13,6 +13,7 @@ MINIO_DB_BUCKET_NAME=dbdumps MINIO_ARTIFACTS_BUCKET_NAME=artifacts MINIO_ENDPOINT=localhost + DB_HOST=database DEV=true DB_DATABASE=dbname @@ -26,18 +27,20 @@ ENTITIES=dist/**/*.entity.js SEED=true VITE_USE_FAKE_OAUTH_FOR_DEVELOPMENT=true -GOOGLE_KEY_FILE=grandtourdatasets-5295745f7fab.json -GOOGLE_CLIENT_ID=237481987385-av4suqf7p0qjkmvjo8bc6jdf3g6nb0u7.apps.googleusercontent.com -GOOGLE_CLIENT_SECRET=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 +GOOGLE_KEY_FILE=google-service-account.json JWT_SECRET=SECRET -# this should be replaced with your own oauth credentials -GITHUB_CLIENT_ID=some-client-id -GITHUB_CLIENT_SECRET=some-client-secret +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= -# can be left empty if you don't want to use docker hub DOCKER_HUB_USERNAME= DOCKER_HUB_PASSWORD= VITE_DOCKER_HUB_NAMESPACE= -ARTIFACTS_UPLOADER_IMAGE=rslethz/grandtour-datasets:artifact-uploader-latest \ No newline at end of file +ARTIFACTS_UPLOADER_IMAGE=rslethz/kleinkram:artifact-uploader-latest + +# Docker socket group ID (run: stat -c '%g' /var/run/docker.sock) +DOCKER_GID=984 diff --git a/.github/AGENTS.md b/.github/AGENTS.md new file mode 100644 index 000000000..2a0bd62e1 --- /dev/null +++ b/.github/AGENTS.md @@ -0,0 +1,113 @@ +# AI Agent Instructions + +This document provides a quick reference for AI agents working on the Kleinkram project. For detailed information, always refer to the official documentation in the `../docs/` directory. + +## 🚀 Quick Start (Launch) + +To launch the full application stack: + +```bash +docker compose up --build --watch -d +``` + +**Access Points:** + +- **API Server**: [http://localhost:3000](http://localhost:3000) +- **Frontend**: [http://localhost:8003](http://localhost:8003) +- **MinIO Console**: [http://localhost:9001](http://localhost:9001) +- **Documentation**: [http://localhost:4000](http://localhost:4000) + +> [!NOTE] +> See [../docs/development/getting-started.md](../docs/development/getting-started.md) for detailed setup instructions. + +## 🛠️ Development Workflow + +### API Interaction + +**IMPORTANT**: All API calls to the backend must include the `kleinkram-client-version` header matching the current app version (e.g., `0.56.0`). + +```typescript +headers: { + 'kleinkram-client-version': '0.56.0', // Replace with actual version from package.json + ... +} +``` + +**Exception:** The `/api/health` endpoint does not require this header. + +### Formatting & Linting + +Run these commands before submitting changes: + +```bash +# Format Python code +black . + +# Format TypeScript/JavaScript/JSON/Markdown +yarn run prettier + +# Run Linting +yarn run eslint-full:quiet +``` + +### Testing + +**Backend Tests:** + +```bash +# Run all tests +yarn test + +# Run specific test file +npx jest tests/actions/action-file-events.test.ts --runInBand --detectOpenHandles --forceExit +``` + +**CLI/Python Tests:** + +```bash +# Run pytest (ensure virtualenv is active) +pytest +``` + +> [!NOTE] +> See [../docs/development/testing/getting-started.md](../docs/development/testing/getting-started.md) for detailed testing guide. + +## 📚 Documentation Reference + +- **General Setup**: [../docs/development/getting-started.md](../docs/development/getting-started.md) +- **Testing**: [../docs/development/testing/getting-started.md](../docs/development/testing/getting-started.md) +- **Python/CLI**: [../docs/development/python/getting-started.md](../docs/development/python/getting-started.md) + +## 🧪 Test Naming Conventions + +To ensure consistency and readability, please adhere to the following naming conventions for test files and test cases: + +### File Naming + +- **Format**: `kebab-case.test.ts` +- **Location**: `tests//` or `tests/functional/` +- **Examples**: + - `user-authentication.test.ts` + - `file-upload.test.ts` + - `action-execution.test.ts` + +### Test Suite Naming (`describe`) + +- **Format**: Title Case or Sentence case describing the component or feature under test. +- **Examples**: + - `describe('User Authentication', ...)` + - `describe('File Upload Service', ...)` + +### Test Case Naming (`test` / `it`) + +- **Format**: Should start with a verb (e.g., "should", "if") and describe the expected behavior or scenario. +- **Style**: + - **"should" style**: `test('should return 200 OK when valid credentials are provided', ...)` + - **"if" style**: `test('if a user can view details of a submitted action', ...)` +- **Clarity**: Be specific about the condition and the expected result. + +### General Rules + +- Keep tests focused on a single behavior. +- Use descriptive variable names within tests. +- Clean up resources in `afterEach` or `afterAll` to prevent state leakage. diff --git a/.github/workflows/test.yml b/.github/workflows/api-tests.yml similarity index 50% rename from .github/workflows/test.yml rename to .github/workflows/api-tests.yml index 6cc1a39a3..12f806a9a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/api-tests.yml @@ -2,65 +2,91 @@ name: Test The Application on: pull_request: - types: [opened, synchronize, reopened, edited] + types: [opened, synchronize, reopened] branches: - main - dev jobs: - test_API: + test-api: runs-on: [self-hosted, linux] - # Permissions reduced since we are no longer posting comments/checks permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v4 + with: + run_install: false - name: Use Node.js uses: actions/setup-node@v6 with: node-version: '22' - package-manager-cache: false - - - name: Install yarn - run: npm install -g yarn + cache: 'pnpm' - name: install dependencies - run: yarn install --immutable + run: pnpm install --frozen-lockfile + + - name: Build workspace packages + run: pnpm -r run build + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Generate test data + run: | + pip install rosbags + python3 cli/tests/generate_test_data.py - name: build_stack run: docker compose -f docker-compose.testing.yml build + env: + BACKEND_URL: 'http://localhost:3000' + GIT_BRANCH: ${{ github.head_ref || github.ref_name }} + GIT_COMMIT: ${{ github.sha }} - name: clear previous volumes run: docker compose -f docker-compose.testing.yml down -v + - name: Pre-generate endpoints + working-directory: ./backend + run: | + mkdir -p .endpoints + npm run gen:docs -- .endpoints + - name: launch_stack working-directory: ./backend run: | docker compose -f ../docker-compose.testing.yml up -d SECONDS=0 TIMEOUT=180 - while [ ! -f ./.endpoints/__generated__endpoints.json ]; do + # wait for the api to be reachable + while ! curl -s http://localhost:3000/ > /dev/null; do if [ $SECONDS -ge $TIMEOUT ]; then - echo "Timeout reached: __generated__endpoints.json not found" + echo "Timeout reached: API not reachable" + docker logs kleinkram-api-server-test docker compose -f ../docker-compose.testing.yml down exit 1 fi sleep 1 - echo "waiting for endpoints" + echo "waiting for API to be reachable..." done - echo "Endpoints found. Running tests..." + + echo "API is up. Running tests..." # This will now cause the step to fail immediately if tests fail. - yarn test --ci + pnpm test --ci echo "Tests passed successfully." - name: Clean up if: always() - run: npm uninstall -g yarn + run: echo "Cleanup done" - name: Stop Docker stack if: always() diff --git a/.github/workflows/build-sample-actions.yml b/.github/workflows/build-sample-actions.yml new file mode 100644 index 000000000..1f753493f --- /dev/null +++ b/.github/workflows/build-sample-actions.yml @@ -0,0 +1,67 @@ +name: Build Sample Actions + +on: + workflow_dispatch: + inputs: + kleinkram_version: + description: 'Kleinkram version to install' + required: false + default: '' + workflow_run: + workflows: ['Update PyPI'] + types: + - completed + push: + paths: + - 'examples/kleinkram-actions/**' + +jobs: + build-and-push: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + strategy: + matrix: + action: + [ + validate-data, + extract-metadata, + convert-formats, + python-template, + ] + type: [dev, prod] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Determine Tag + id: tag + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "tag=dev" >> $GITHUB_OUTPUT + echo "kleinkram_version=${{ inputs.kleinkram_version }}" >> $GITHUB_OUTPUT + elif [ "${{ matrix.type }}" == "dev" ]; then + echo "tag=dev" >> $GITHUB_OUTPUT + echo "kleinkram_version=" >> $GITHUB_OUTPUT # Installs latest from PyPI which is updated by Update PyPI workflow + else + echo "tag=latest" >> $GITHUB_OUTPUT + echo "kleinkram_version=" >> $GITHUB_OUTPUT # Installs latest from PyPI + fi + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: examples/kleinkram-actions/${{ matrix.action }} + push: true + tags: rslethz/action:${{ matrix.action }}-${{ steps.tag.outputs.tag }} + build-args: | + KLEINKRAM_VERSION=${{ steps.tag.outputs.kleinkram_version }} diff --git a/.github/workflows/check-migrations.yml b/.github/workflows/check-migrations.yml new file mode 100644 index 000000000..eb9008639 --- /dev/null +++ b/.github/workflows/check-migrations.yml @@ -0,0 +1,32 @@ +name: Check Migrations + +on: + pull_request: + branches: + - main + +jobs: + check-migrations: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + working-directory: backend + + - name: Check migrations + run: pnpm run migration:check + working-directory: backend diff --git a/.github/workflows/check_version_number.yml b/.github/workflows/check-version-number.yml similarity index 70% rename from .github/workflows/check_version_number.yml rename to .github/workflows/check-version-number.yml index 5493eccd9..794dddd1f 100644 --- a/.github/workflows/check_version_number.yml +++ b/.github/workflows/check-version-number.yml @@ -8,22 +8,25 @@ on: jobs: check-version-number: - name: check-version-number runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v4 + with: + run_install: false - uses: actions/setup-node@v6 with: node-version: '22' - package-manager-cache: false + cache: 'pnpm' - name: Validate Version Number by calling grunt validateVersions run: | - yarn install --immutable --ignore-scripts - yarn run validateVersions + pnpm install --frozen-lockfile --ignore-scripts + pnpm run validateVersions working-directory: . - uses: del-systems/check-if-version-bumped@v2 diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml new file mode 100644 index 000000000..da3d18270 --- /dev/null +++ b/.github/workflows/cli-tests.yml @@ -0,0 +1,143 @@ +name: CLI Integration Tests + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: + - main + - dev + +jobs: + cli-tests: + runs-on: [self-hosted, linux] + + permissions: + contents: read + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Build Docker stack + run: docker compose -f docker-compose.testing.yml build + env: + BACKEND_URL: 'http://localhost:3000' + GIT_BRANCH: ${{ github.head_ref || github.ref_name }} + GIT_COMMIT: ${{ github.sha }} + + - name: Clear previous volumes + run: docker compose -f docker-compose.testing.yml down -v + + - name: Launch Docker stack + run: | + docker compose -f docker-compose.testing.yml up -d + echo "Docker stack started" + + - name: Generate mock test data + run: | + # Install dependencies for data generation + pip install rosbags + + # Generate valid bag files using the script + python3 cli/tests/generate_test_data.py + + echo "Mock test data generated" + + - name: Install CLI + working-directory: ./cli + run: | + # Create virtual environment + python -m venv .venv + source .venv/bin/activate + + # Install CLI in development mode and dev dependencies + pip install -e . -r requirements.txt -r requirements-dev.txt + + # Verify installation + klein --version + + echo "CLI installed successfully" + + - name: Wait for backend to be ready + run: | + SECONDS=0 + TIMEOUT=180 + echo "Waiting for backend to be ready..." + + while ! curl -s http://localhost:3000/auth/available-providers > /dev/null; do + if [ $SECONDS -ge $TIMEOUT ]; then + echo "Timeout reached: Backend not ready" + docker compose -f docker-compose.testing.yml logs api-server + docker compose -f docker-compose.testing.yml down + exit 1 + fi + sleep 2 + echo "Waiting for backend... ($SECONDS seconds elapsed)" + done + + echo "Backend is ready!" + + - name: Configure CLI endpoint + working-directory: ./cli + run: | + source .venv/bin/activate + + # Remove existing config to avoid compatibility check prompt + rm -f ~/.kleinkram.json + + # Set endpoint to local + klein endpoint local + + echo "CLI endpoint configured to local" + + - name: Authenticate with fake OAuth (User 1 - Admin) + working-directory: ./cli + run: | + source .venv/bin/activate + + # Authenticate as admin user using auto-select + klein login --oauth-provider fake-oauth --user 1 + + echo "Authenticated as admin user" + + - name: Run CLI tests + working-directory: ./cli + run: | + source .venv/bin/activate + + # Run pytest with verbose output + pytest tests/ -v --tb=short + + echo "CLI tests completed successfully" + + - name: Test with different user (User 2 - Internal) + working-directory: ./cli + run: | + source .venv/bin/activate + + # Re-authenticate as internal user + klein login --oauth-provider fake-oauth --user 2 + + # Run a subset of tests that check permissions + # (This is optional - only if you have permission-specific tests) + echo "Authenticated as internal user for permission testing" + + - name: Clean up + if: always() + run: | + # Stop Docker stack + docker compose -f docker-compose.testing.yml down + docker compose down fake-oauth + + # Clean up test data + rm -rf cli/data/testing + + echo "Cleanup completed" + + - name: Delete Docker volumes + if: always() + run: docker volume prune -f diff --git a/.github/workflows/create_release.yml b/.github/workflows/create-release.yml similarity index 93% rename from .github/workflows/create_release.yml rename to .github/workflows/create-release.yml index d98d699ee..6808616e6 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create-release.yml @@ -7,13 +7,12 @@ on: - main jobs: - create_release_tag: - name: Create Release + create-release: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: diff --git a/.github/workflows/dev_build.yml b/.github/workflows/dev-build.yml similarity index 71% rename from .github/workflows/dev_build.yml rename to .github/workflows/dev-build.yml index 495a84878..367021820 100644 --- a/.github/workflows/dev_build.yml +++ b/.github/workflows/dev-build.yml @@ -5,12 +5,12 @@ on: branches: ['dev'] jobs: - kleinkram_build: + dev-build: environment: 'BuildEnv' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Login to DockerHub uses: docker/login-action@v3 with: @@ -20,7 +20,9 @@ jobs: name: Build the Docker compose run: docker compose -f docker-compose.dev.yml build env: - QUASAR_ENDPOINT: ${{ vars.QUASAR_ENDPOINT_DEV }} + BACKEND_URL: ${{ vars.BACKEND_URL_DEV }} + GIT_BRANCH: ${{ github.ref_name }} + GIT_COMMIT: ${{ github.sha }} - working-directory: . name: push the docker image - run: docker compose -f docker-compose.dev.yml push + run: docker compose -f docker-compose.dev.yml push api-server frontend queue-consumer documentation artifact-uploader diff --git a/.github/workflows/draft-pdf.yml b/.github/workflows/draft-pdf.yml index 796a98f80..0591e8009 100644 --- a/.github/workflows/draft-pdf.yml +++ b/.github/workflows/draft-pdf.yml @@ -6,12 +6,11 @@ on: - .github/workflows/draft-pdf.yml jobs: - paper: + draft-pdf: runs-on: ubuntu-latest - name: Paper Draft steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Build draft PDF uses: openjournals/openjournals-draft-action@master with: diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 8807ae4c4..4c543bb7b 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -2,7 +2,7 @@ on: push: branches: ['dev'] pull_request: - types: [opened, synchronize, reopened, edited] + types: [opened, synchronize, reopened] branches: - dev @@ -11,37 +11,47 @@ jobs: runs-on: [self-hosted, linux] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v4 + with: + run_install: false - name: Use Node.js uses: actions/setup-node@v6 with: node-version: '22' - package-manager-cache: false + cache: 'pnpm' - name: Generate Build Info run: bash create_build_info.sh working-directory: frontend - - name: Install yarn - run: npm install -g yarn - - # for eslint we may need to install the dependencies - name: Install dependencies - run: yarn install --immutable + run: pnpm install --frozen-lockfile + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install pre-commit + run: pip install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files --show-diff-on-failure + env: + SKIP: eslint,prettier - name: Prepare Quasar - run: yarn quasar prepare + run: pnpm exec quasar prepare working-directory: frontend - - name: Run ESLint - run: | - if ! yarn eslint-full:check; then - echo "ESLint check failed. Run 'yarn eslint-full' locally to reproduce." - exit 1 # Explicitly fail the step - fi - - # Uninstall when you're done - - name: Clean up - if: always() - run: npm uninstall -g yarn + - name: Type Check + run: pnpm run type-check + + - name: Lint Check + run: pnpm run lint:check + + - name: Run Prettier Check + run: pnpm run prettier:check diff --git a/.github/workflows/example_actions.yml b/.github/workflows/example_actions.yml deleted file mode 100644 index 3cae5472f..000000000 --- a/.github/workflows/example_actions.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Build example actions in /examples - -on: - push: - branches: ['main', 'dev'] - paths: - - 'examples/**' - - 'cli/**' - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Determine build environment - run: echo "::set-output name=env::${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}" - id: set-env - - - name: Build the Docker compose - run: sh buildPush.sh ${{ steps.set-env.outputs.env }} - working-directory: examples diff --git a/.github/workflows/production_build.yml b/.github/workflows/production-build.yml similarity index 66% rename from .github/workflows/production_build.yml rename to .github/workflows/production-build.yml index d246ac0cc..d0c32eeff 100644 --- a/.github/workflows/production_build.yml +++ b/.github/workflows/production-build.yml @@ -5,12 +5,12 @@ on: branches: ['main'] jobs: - GrandTourBuild: + production-build: environment: 'BuildEnv' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Login to DockerHub uses: docker/login-action@v3 with: @@ -20,7 +20,9 @@ jobs: name: Build the Docker compose run: docker compose -f docker-compose.prod.yml build env: - QUASAR_ENDPOINT: ${{ vars.QUASAR_ENDPOINT }} + BACKEND_URL: ${{ vars.BACKEND_URL }} + GIT_BRANCH: ${{ github.ref_name }} + GIT_COMMIT: ${{ github.sha }} - working-directory: . - name: push the docker -f docker-compose.prod.yml image - run: docker compose -f docker-compose.prod.yml push + name: push the docker image + run: docker compose -f docker-compose.prod.yml push api-server frontend queue-consumer documentation artifact-uploader diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml new file mode 100644 index 000000000..8fc3f6218 --- /dev/null +++ b/.github/workflows/python-lint.yml @@ -0,0 +1,20 @@ +name: Lint + +on: + push: + branches: + - dev + paths: + - 'cli/**' + +jobs: + python-lint: + runs-on: [self-hosted, linux] + steps: + - uses: actions/checkout@v4 + - name: Install and run Black + run: | + sudo apt update + sudo apt install -y python3-pip + python3 -m pip install black --break-system-packages + black --check . diff --git a/.github/workflows/pythonLint.yml b/.github/workflows/pythonLint.yml deleted file mode 100644 index 8c2cbf3d2..000000000 --- a/.github/workflows/pythonLint.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Lint - -on: - push: - branches: - - dev - paths: - - 'cli/**' - -jobs: - lint: - runs-on: [self-hosted, linux] - steps: - - uses: actions/checkout@v5 - - name: Install python3-venv - run: sudo apt update && sudo apt install -y python3.10-venv - - uses: psf/black@stable diff --git a/.github/workflows/updatePypi.yml b/.github/workflows/update-pypi.yml similarity index 63% rename from .github/workflows/updatePypi.yml rename to .github/workflows/update-pypi.yml index e870aebfb..07be70f39 100644 --- a/.github/workflows/updatePypi.yml +++ b/.github/workflows/update-pypi.yml @@ -7,18 +7,23 @@ on: - dev paths: - 'cli/**' + - '.github/workflows/updatePypi.yml' jobs: - build_and_deploy: + update-pypi: runs-on: ubuntu-latest + permissions: + contents: read + actions: write + id-token: write steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: - python-version: '3.11' # Match your project's required Python version + python-version: '3.12' - name: Install dependencies run: | @@ -47,3 +52,15 @@ jobs: password: ${{ secrets.PYPI_API_TOKEN }} package: cli/dist/* packages_dir: cli/dist + + - name: Get version from setup.cfg + id: get_version + run: | + VERSION=$(grep "version =" cli/setup.cfg | cut -d = -f 2 | xargs) + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Trigger Build Sample Actions + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh workflow run build-sample-actions.yml -f kleinkram_version=$VERSION --ref ${{ github.ref }} diff --git a/.gitignore b/.gitignore index b0d94ed05..ee5d43ed4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,12 @@ queueConsumer/src/artifactUpload/anymal-grand-tour-3b7a5d0c8ef4.json /.eslintcache -/**/dist/tsconfig.tsbuildinfo +*.tsbuildinfo +dist/ +packages/*/dist/ # Lockfiles for npm and pnpm, we are using yarn **/package-lock.json -**/pnpm-lock.yaml \ No newline at end of file +docs/development/api/generated/ +docs/development/application-structure/postgres.md +docs/development/application-structure/postgres.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..e6b1c9cf1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,46 @@ +exclude: 'yarn\.lock|pnpm-lock\.yaml|frontend/src/build\.ts' +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-docstring-first + - id: check-yaml + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: trailing-whitespace + + - repo: https://github.com/psf/black + rev: 25.11.0 + hooks: + - id: black + language_version: python3 + + - repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + files: ^cli/ + args: [--config, cli/setup.cfg] + + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + files: ^cli/ + args: [--sl] + + - repo: local + hooks: + - id: eslint + name: eslint + entry: env NODE_OPTIONS='--max-old-space-size=8192' node_modules/.bin/eslint --fix --cache --config eslint.config.mjs + language: system + types_or: [javascript, ts, vue] + + - repo: local + hooks: + - id: prettier + name: prettier + entry: node_modules/.bin/prettier --write --cache --experimental-cli + language: system + types_or: [css, javascript, ts, tsx, json, yaml, markdown, vue] diff --git a/.prettierignore b/.prettierignore index fd2e3aaf3..469a50357 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,6 +11,8 @@ **/__generated__endpoints.json **/.mypy_cache **/cli +pnpm-lock.yaml +**/.pnpm-store # ignore bag and mcap files **/*.mcap @@ -21,4 +23,4 @@ **/*.yaml # ignore build info file -frontend/src/build.ts \ No newline at end of file +frontend/src/build.ts diff --git a/Gruntfile.js b/Gruntfile.js index 4163108f3..8a46acf0f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,7 +7,11 @@ module.exports = function (grunt) { grunt.initConfig({ bump: { options: { - files: ['package.json', '*/package.json'], + files: [ + 'package.json', + '*/package.json', + 'packages/*/package.json', + ], commit: false, push: false, createTag: false, @@ -92,7 +96,10 @@ module.exports = function (grunt) { const version = packageJson.version; console.log(`Checking all versions match ${version}`); - const files = grunt.file.expand('*/package.json'); + const files = grunt.file.expand([ + '*/package.json', + 'packages/*/package.json', + ]); files.push('package.json'); files.forEach((file) => { diff --git a/README.md b/README.md index cdbc13ce6..8371493fe 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,61 @@ -# Kleinkram - A structured bag and mcap storage solution +![Kleinkram Cover](docs/assets/landingpage.png) -Kleinkram is a structured bag and mcap file storage solution for ROS1 and ROS2. -It is designed to be a simple and efficient way to store and retrieve bag and mcap files. -Kleinkram is being developed by the [Robotic Systems Lab (RSL) at ETH Zurich](https://rsl.ethz.ch/). +# Kleinkram - Open Robotic Data Management -## Features +Kleinkram is a self-hosted, open-source platform for managing and processing robotics data. It provides a structured way to store, organize, and act on your data. -- **Simple**: Kleinkram is designed to be simple to use and easy to understand. -- **Efficient**: Kleinkram is designed to be efficient in terms of storage and retrieval. -- **Structured**: Kleinkram is designed to be structured in terms of storage and retrieval. -- **ROS1 and ROS2**: Kleinkram is designed to work with both ROS1 and ROS2. +- **Organize**: Structure data in Projects and Missions. +- **Store**: Support for ROS bags (`.bag`, `.mcap`), ZED camera recordings (`.svo2`), and configs (`.yml`). +- **Process**: Run automated actions (validation, conversion, extraction) using Kleinkram Actions. +- **Collaborate**: Share data with granular access control. -## Getting Started +## Documentation + +For full documentation, please visit [docs.datasets.leggedrobotics.com](https://docs.datasets.leggedrobotics.com/). -Consult the [Getting Started](https://docs.datasets.leggedrobotics.com/usage/getting-started.html) guide for users or -the [Getting Started](https://docs.datasets.leggedrobotics.com/development/getting-started.html) guide for developers. +## Try Kleinkram Locally -## Installation +You can easily run a local instance of Kleinkram to try it out. This has been tested on Ubuntu 24.04 and macOS. -Clone the repository: +1. **Clone the repository** ```bash git clone git@github.com:leggedrobotics/kleinkram.git cd kleinkram ``` -Now you can run Kleinkram using `docker compose` +2. **Start the application** ```bash docker compose up --build ``` -This will launch the frontend under `http://localhost:8003`, -the minio console at `http://localhost:9001`, the documentation -at `http://localhost:4000` and the api at `http://localhost:3000`. +
+Why --build? +The --build flag ensures that the Docker images are built before starting. For more details on Docker Compose, see the official docs. +
-## Documentation +> [!WARNING] +> If you have run Kleinkram locally before, you should consider deleting all existing data. See the Try Kleinkram Locally documentation for more details. + +3. **Access the application** + +You can now access the frontend at `http://localhost:8003`. + +> [!TIP] +> **Browser Compatibility** +> Make sure to use Chrome or Firefox for the best experience with the local development server. There are some known issues related to Safari when running Kleinkram locally. + +4. **Configure CLI (Optional)** + +If you want to use the CLI with your local instance, you need to set the endpoint to local: + +```bash +klein endpoint local +klein login +``` -You can read the documentation deployed by RSL at [docs.datasets.leggedrobotics.com](https://docs.datasets.leggedrobotics.com/). +
+Start Developing +For a deeper dive into the project structure and development workflow, see the Application Structure documentation. Your development environment is designed for Ubuntu 24.04. +
diff --git a/backend/.gitignore b/backend/.gitignore index 5f3a641f4..c248d7008 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -58,5 +58,15 @@ pids # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json /grandtourdatasets-5295745f7fab.json -/migration/*/migrations/ + /junit.xml + +# ignore bag and mcap files +*.bag +*.mcap + +# OpenApi +swagger.json +*.md +api-modules.json +.endpoints/ diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 18c151c33..000000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM node:24-alpine - -COPY ./backend/package.json /usr/src/app/backend/ -COPY ./backend/yarn.lock /usr/src/app/backend/ -COPY ./common/package.json /usr/src/app/common/ -COPY ./common/yarn.lock /usr/src/app/common/ - -WORKDIR /usr/src/app/backend -RUN yarn --immutable - -WORKDIR /usr/src/app/common -RUN yarn --immutable - -RUN apk --no-cache add postgresql-client -# copy the rest of the files -COPY ./backend /usr/src/app/backend -COPY ./common /usr/src/app/common - -# build the app -WORKDIR /usr/src/app/backend -# marke the entrypoint.sh as executable -RUN chmod +x entrypoint.sh -CMD ["sh", "entrypoint.sh"] diff --git a/backend/api-modules.md b/backend/api-modules.md new file mode 100644 index 000000000..5666704d4 --- /dev/null +++ b/backend/api-modules.md @@ -0,0 +1,17 @@ +| Module | Path | Description | +| :---------------------------- | :----------------------- | :------------------------- | +| [`access`](access.md) | `/access` | Docs for access module | +| [`actions`](actions.md) | `/actions` | Docs for actions module | +| [`health`](health.md) | `/api/health` | Docs for health module | +| [`auth`](auth.md) | `/auth` | Docs for auth module | +| [`category`](category.md) | `/category` | Docs for category module | +| [`file`](file.md) | `/files` | Docs for file module | +| [`foxglove`](foxglove.md) | `/integrations/foxglove` | Docs for foxglove module | +| [`mission`](mission.md) | `/mission` | Docs for mission module | +| [`oldproject`](oldproject.md) | `/oldProject` | Docs for oldproject module | +| [`project`](project.md) | `/projects` | Docs for project module | +| [`tag`](tag.md) | `/tag` | Docs for tag module | +| [`templates`](templates.md) | `/templates` | Docs for templates module | +| [`topic`](topic.md) | `/topic` | Docs for topic module | +| [`user`](user.md) | `/user` | Docs for user module | +| [`worker`](worker.md) | `/worker` | Docs for worker module | diff --git a/backend/check-version.ts b/backend/check-version.ts new file mode 100644 index 000000000..369576ad4 --- /dev/null +++ b/backend/check-version.ts @@ -0,0 +1,3 @@ +import { appVersion } from './src/app-version'; +// eslint-disable-next-line no-console +console.log('App Version:', appVersion); diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh old mode 100644 new mode 100755 index 2c5aba87a..6a042b873 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -1,22 +1,27 @@ #!/bin/sh -echo "Start Backend" if [ "$DEVBUILD" = "true" ]; then echo "Development Build" - yarn start:dev & -else - echo "Production Build" - yarn start:prod & -fi + npm run gen:docs /app/docs/development/api/generated & + pnpm start:dev & + sleep 10 -sleep 10 -cd ../common + # Only try to access packages/seed in development where the volume is mounted + cd ../packages/backend-common || echo "Could not find backend-common, skipping seed" + + if [ "$SEED" = "true" ]; then + echo "Seeding Database" + pnpm seed:run + else + echo "Not Seeding Database" + fi + + echo "----------------------------------------" + echo "Backend API ready on http://localhost:3000" + echo "----------------------------------------" -if [ "$SEED" = "true" ]; then - echo "Seeding Database" - yarn seed:run else - echo "Not Seeding Database" - tail -f /dev/null + echo "Production Build" + node dist/main.js & fi tail -f /dev/null diff --git a/backend/jest.config.js b/backend/jest.config.js index d8e515994..52391042d 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -2,13 +2,33 @@ module.exports = { testEnvironment: 'node', transform: { - '^.+.tsx?$': ['ts-jest', {}], + '^.+\\.[tj]sx?$': [ + 'ts-jest', + { + tsconfig: 'tsconfig.test.json', + }, + ], }, moduleNameMapper: { + '^@/(.*)$': '/src/$1', '^@common/(.*)$': '/../common/$1', + '^@kleinkram/backend-common$': + '/../packages/backend-common/src/index.ts', + '^@kleinkram/backend-common/(.*)$': + '/../packages/backend-common/src/$1', + '^@backend-common/(.*)$': '/../packages/backend-common/src/$1', + '^@kleinkram/shared$': '/../packages/shared/src/index.ts', + '^@kleinkram/shared/(.*)$': '/../packages/shared/src/$1', + '^@kleinkram/validation$': + '/../packages/validation/src/index.ts', + '^@kleinkram/validation/(.*)$': + '/../packages/validation/src/$1', + '^@kleinkram/api-dto$': '/../packages/api-dto/src/index.ts', + '^@kleinkram/api-dto/(.*)$': '/../packages/api-dto/src/$1', }, preset: 'ts-jest', modulePathIgnorePatterns: ['/dist/'], + transformIgnorePatterns: [], reporters: ['/tests/utils/reporter.js'], }; diff --git a/backend/migration/dev/development-datasource.config.ts b/backend/migration/dev/development-datasource.config.ts index 8f8f0531d..06fe03707 100644 --- a/backend/migration/dev/development-datasource.config.ts +++ b/backend/migration/dev/development-datasource.config.ts @@ -4,22 +4,22 @@ import { DataSourceOptions } from 'typeorm'; dotenv.config({ path: './migration/.env' }); -const databasePort: string | undefined = process.env['dev_port']; +const databasePort: string | undefined = process.env.dev_port; export function getConfig(): DataSourceOptions { return { type: 'postgres', - host: process.env['dev_dbhost'], + host: process.env.dev_dbhost, port: Number.parseInt(databasePort ?? '5432', 10), - ssl: process.env['dev_ssl'] === 'true', - username: process.env['dev_dbuser'], - password: process.env['dev_dbpassword'], - database: process.env['dev_dbname'], + ssl: process.env.dev_ssl === 'true', + username: process.env.dev_dbuser, + password: process.env.dev_dbpassword, + database: process.env.dev_dbname, synchronize: false, - migrations: ['migration/dev/migrations/*.ts'], + migrations: ['migration/migrations/*.ts'], entities: [ - '../common/entities/**/*.entity{.ts,.js}', - '../common/viewEntities/**/*.entity{.ts,.js}', + '../packages/backend-common/src/entities/**/*.entity{.ts,.js}', + '../packages/backend-common/src/viewEntities/**/*.entity{.ts,.js}', ], } as DataSourceOptions; } diff --git a/backend/migration/dev/migration.config.ts b/backend/migration/dev/migration.config.ts index 652c744bd..61e2cb111 100644 --- a/backend/migration/dev/migration.config.ts +++ b/backend/migration/dev/migration.config.ts @@ -1,3 +1,4 @@ +import 'tsconfig-paths/register'; import { DataSource } from 'typeorm'; import { getConfig } from './development-datasource.config'; @@ -5,6 +6,7 @@ const datasource = new DataSource(getConfig()); datasource .initialize() + // eslint-disable-next-line no-console .then(console.log) // eslint-disable-next-line unicorn/prefer-top-level-await .catch((error: unknown) => { diff --git a/backend/migration/example.env b/backend/migration/example.env index b3069826e..8ccaecd92 100644 --- a/backend/migration/example.env +++ b/backend/migration/example.env @@ -1,23 +1,23 @@ -prod_dbpassword=passwört -dev_dbpassword=anotherPässwörd -local_dbpassword=theLocalPässwörd +prod_dbpassword= +dev_dbpassword= +local_dbpassword=dbuserpass -prod_port=443 -dev_port=443 +prod_port= +dev_port= local_port=5432 -prod_dbname=grandtour -dev_dbname=grandtour +prod_dbname= +dev_dbname= local_dbname=dbname -prod_dbuser=postgress_user -dev_dbuser=postgress_user +prod_dbuser= +dev_dbuser= local_dbuser=dbuser -prod_dbhost=db.datasets.leggedrobotics.com -dev_dbhost=db.datasets.dev.leggedrobotics.com +prod_dbhost= +dev_dbhost= local_dbhost=localhost prod_ssl=true dev_ssl=true -local_ssl=false \ No newline at end of file +local_ssl=false diff --git a/backend/migration/local/local-datasource.config.ts b/backend/migration/local/local-datasource.config.ts deleted file mode 100644 index 43ab64c41..000000000 --- a/backend/migration/local/local-datasource.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import dotenv from 'dotenv'; -import * as process from 'node:process'; -import { DataSourceOptions } from 'typeorm'; - -dotenv.config({ path: './migration/.env' }); - -const databasePort = process.env['local_dbport']; - -export function getConfig() { - return { - type: 'postgres', - host: process.env['local_dbhost'], - port: Number.parseInt(databasePort ?? '5432', 10), - ssl: process.env['local_ssl'] === 'true', - username: process.env['local_dbuser'], - password: process.env['local_dbpassword'], - database: process.env['local_dbname'], - synchronize: false, - migrations: ['migration/local/migrations/*.ts'], - entities: [ - '../common/entities/**/*.entity{.ts,.js}', - '../common/viewEntities/**/*.entity{.ts,.js}', - ], - } as DataSourceOptions; -} diff --git a/backend/migration/local/migration.config.ts b/backend/migration/local/migration.config.ts deleted file mode 100644 index 8e87b4e96..000000000 --- a/backend/migration/local/migration.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { DataSource } from 'typeorm'; -import { getConfig } from './local-datasource.config'; - -const datasource = new DataSource(getConfig()); - -datasource - .initialize() - .then(console.log) - // eslint-disable-next-line unicorn/prefer-top-level-await - .catch((error: unknown) => { - console.error(error); - }); -export default datasource; diff --git a/backend/migration/migrations/000000000000-init-db.ts b/backend/migration/migrations/000000000000-init-db.ts new file mode 100644 index 000000000..def39d15e --- /dev/null +++ b/backend/migration/migrations/000000000000-init-db.ts @@ -0,0 +1,475 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InitDB1765270980400 implements MigrationInterface { + name = 'InitDB1765270980400'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "base_entity" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, CONSTRAINT "PK_c576131d32964047fc36dfcdd8b" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."account_provider_enum" AS ENUM('google', 'github', 'fake-oauth')`, + ); + await queryRunner.query( + `CREATE TABLE "account" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "provider" "public"."account_provider_enum" NOT NULL, "oauthID" character varying NOT NULL, CONSTRAINT "provider_oauthID" UNIQUE ("provider", "oauthID"), CONSTRAINT "PK_31e2fd7720a2da3af586f17778f" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."apikey_key_type_enum" AS ENUM('ACTION')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."apikey_rights_enum" AS ENUM('0', '10', '20', '30', '100')`, + ); + await queryRunner.query( + `CREATE TABLE "apikey" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "apikey" uuid NOT NULL DEFAULT uuid_generate_v4(), "key_type" "public"."apikey_key_type_enum" NOT NULL, "rights" "public"."apikey_rights_enum" NOT NULL, "missionUuid" uuid, "userUuid" uuid, CONSTRAINT "UQ_e1a6e9c0229f80fc2604aa3dc61" UNIQUE ("apikey"), CONSTRAINT "PK_c3d118f7d34d4df9ccad270ab10" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."mission_access_rights_enum" AS ENUM('0', '10', '20', '30', '100')`, + ); + await queryRunner.query( + `CREATE TABLE "mission_access" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "rights" "public"."mission_access_rights_enum" NOT NULL, "accessGroupUuid" uuid NOT NULL, "missionUuid" uuid NOT NULL, CONSTRAINT "no_duplicated_access_groups_per_mission" UNIQUE ("accessGroupUuid", "missionUuid"), CONSTRAINT "PK_91e94cb40d7175c7fc4237fa3aa" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."project_access_rights_enum" AS ENUM('0', '10', '20', '30', '100')`, + ); + await queryRunner.query( + `CREATE TABLE "project_access" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "rights" "public"."project_access_rights_enum" NOT NULL, "accessGroupUuid" uuid NOT NULL, "projectUuid" uuid NOT NULL, CONSTRAINT "no_duplicated_access_groups_per_project" UNIQUE ("accessGroupUuid", "projectUuid"), CONSTRAINT "PK_84729ed1241ba0527207a417a95" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."access_group_type_enum" AS ENUM('AFFILIATION', 'PRIMARY', 'CUSTOM')`, + ); + await queryRunner.query( + `CREATE TABLE "access_group" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "name" character varying NOT NULL, "type" "public"."access_group_type_enum" NOT NULL DEFAULT 'CUSTOM', "hidden" boolean NOT NULL DEFAULT false, "creatorUuid" uuid, CONSTRAINT "unique_access_group_name" UNIQUE ("name"), CONSTRAINT "PK_69db32968467cf78399397e3d2c" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TABLE "group_membership" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "expirationDate" TIMESTAMP, "canEditGroup" boolean NOT NULL DEFAULT false, "accessGroupUuid" uuid, "userUuid" uuid, CONSTRAINT "no_duplicated_user_in_access_group" UNIQUE ("accessGroupUuid", "userUuid"), CONSTRAINT "PK_c8e1e8674077673f6a7db02ec36" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TABLE "category" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "name" character varying NOT NULL, "projectUuid" uuid, "creatorUuid" uuid, CONSTRAINT "unique_category_name_per_project" UNIQUE ("name", "projectUuid"), CONSTRAINT "PK_86ee096735ccbfa3fd319af1833" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TABLE "topic" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "name" character varying NOT NULL, "type" character varying NOT NULL, "nrMessages" bigint NOT NULL, "messageEncoding" character varying NOT NULL DEFAULT '', "frequency" double precision NOT NULL, "fileUuid" uuid, CONSTRAINT "PK_de180fa646be66ad9dad729af58" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."file_entity_type_enum" AS ENUM('BAG', 'MCAP', 'YAML', 'SVO2', 'TUM', 'DB3', 'ALL')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."file_entity_state_enum" AS ENUM('OK', 'CORRUPTED', 'UPLOADING', 'ERROR', 'CONVERTING', 'CONVERSION_ERROR', 'LOST', 'FOUND')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."file_entity_origin_enum" AS ENUM('GOOGLE_DRIVE', 'UPLOAD', 'CONVERTED', 'UNKNOWN')`, + ); + await queryRunner.query( + `CREATE TABLE "file_entity" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "date" TIMESTAMP NOT NULL, "filename" character varying NOT NULL, "size" bigint NOT NULL, "type" "public"."file_entity_type_enum" NOT NULL, "state" "public"."file_entity_state_enum" NOT NULL DEFAULT 'OK', "hash" character varying, "origin" "public"."file_entity_origin_enum", "missionUuid" uuid NOT NULL, "creatorUuid" uuid NOT NULL, "parentUuid" uuid, CONSTRAINT "unique_file_name_per_mission" UNIQUE ("filename", "missionUuid"), CONSTRAINT "PK_728a42a0fbdf3fc5f5a42c85e12" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."ingestion_job_state_enum" AS ENUM('0', '10', '20', '21', '22', '23', '30', '40', '41', '42', '43', '44')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."ingestion_job_location_enum" AS ENUM('DRIVE', 'MINIO')`, + ); + await queryRunner.query( + `CREATE TABLE "ingestion_job" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "identifier" character varying NOT NULL, "display_name" character varying NOT NULL DEFAULT '', "state" "public"."ingestion_job_state_enum" NOT NULL DEFAULT '0', "location" "public"."ingestion_job_location_enum" NOT NULL DEFAULT 'MINIO', "processingDuration" integer, "errorMessage" character varying, "missionUuid" uuid, "creatorUuid" uuid, "file_uuid" uuid, CONSTRAINT "PK_00938e6d7367135f29fc98a536d" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TABLE "project" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "name" character varying NOT NULL, "description" character varying NOT NULL, "autoConvert" boolean NOT NULL DEFAULT false, "creatorUuid" uuid NOT NULL, CONSTRAINT "UQ_dedfea394088ed136ddadeee89c" UNIQUE ("name"), CONSTRAINT "PK_bcbc9244374131f3ccb908aa616" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."tag_type_datatype_enum" AS ENUM('STRING', 'NUMBER', 'BOOLEAN', 'DATE', 'LOCATION', 'LINK', 'ANY')`, + ); + await queryRunner.query( + `CREATE TABLE "tag_type" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "name" character varying NOT NULL, "description" character varying, "datatype" "public"."tag_type_datatype_enum" NOT NULL, CONSTRAINT "PK_92bf1776a059ea820f934bf2046" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TABLE "tag" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "STRING" character varying, "NUMBER" double precision, "BOOLEAN" boolean, "DATE" TIMESTAMP, "LOCATION" character varying, "missionUuid" uuid, "tagTypeUuid" uuid, "creatorUuid" uuid, CONSTRAINT "PK_d70de2c1e1a3b52adb904028ea2" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."user_role_enum" AS ENUM('ADMIN', 'USER')`, + ); + await queryRunner.query( + `CREATE TABLE "user" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "name" character varying NOT NULL, "email" character varying NOT NULL, "role" "public"."user_role_enum" NOT NULL DEFAULT 'USER', "hidden" boolean NOT NULL DEFAULT false, "avatarUrl" character varying, "account_uuid" uuid, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "REL_86922df84877163a10338c04b9" UNIQUE ("account_uuid"), CONSTRAINT "PK_a95e949168be7b7ece1a2382fed" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."action_template_accessrights_enum" AS ENUM('0', '10', '20', '30', '100')`, + ); + await queryRunner.query( + `CREATE TABLE "action_template" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "image_name" character varying NOT NULL, "name" character varying NOT NULL, "description" character varying NOT NULL DEFAULT '', "command" character varying, "version" integer NOT NULL DEFAULT '1', "isArchived" boolean NOT NULL DEFAULT false, "cpuCores" integer NOT NULL, "cpuMemory" integer NOT NULL, "gpuMemory" integer NOT NULL, "maxRuntime" integer NOT NULL, "entrypoint" character varying, "accessRights" "public"."action_template_accessrights_enum" NOT NULL, "creatorUuid" uuid, CONSTRAINT "unique_versioned_action_name" UNIQUE ("name", "version"), CONSTRAINT "PK_2bb7f72e54fe56c5413945ad8ea" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TABLE "worker" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "identifier" character varying NOT NULL, "hostname" character varying NOT NULL, "cpuMemory" integer NOT NULL, "gpuModel" character varying, "gpuMemory" integer NOT NULL DEFAULT '-1', "cpuCores" integer NOT NULL, "cpuModel" character varying NOT NULL, "storage" integer NOT NULL, "lastSeen" TIMESTAMP NOT NULL, "reachable" boolean NOT NULL, CONSTRAINT "UQ_187e709b9ca210ebafd74e59746" UNIQUE ("identifier"), CONSTRAINT "PK_52e67de404e8f86a670a5ba9359" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."action_state_enum" AS ENUM('PENDING', 'STARTING', 'PROCESSING', 'STOPPING', 'DONE', 'FAILED', 'UNPROCESSABLE')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."action_artifacts_enum" AS ENUM('10', '20', '30', '40')`, + ); + await queryRunner.query( + `CREATE TABLE "action" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "state" "public"."action_state_enum" NOT NULL, "container" json, "state_cause" character varying, "executionStartedAt" TIMESTAMP, "executionEndedAt" TIMESTAMP, "actionContainerStartedAt" TIMESTAMP, "actionContainerExitedAt" TIMESTAMP, "logs" json, "auditLogs" json DEFAULT '[]', "exit_code" integer, "artifact_path" character varying, "artifacts" "public"."action_artifacts_enum" NOT NULL DEFAULT '10', "artifact_size" integer, "artifact_files" json, "image" json, "creatorUuid" uuid NOT NULL, "missionUuid" uuid NOT NULL, "keyUuid" uuid, "templateUuid" uuid NOT NULL, "workerUuid" uuid, CONSTRAINT "REL_15c15b200913cf5234c0668cea" UNIQUE ("keyUuid"), CONSTRAINT "PK_3d098eae105d38b5f16e2156bd7" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TABLE "mission" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "name" character varying NOT NULL, "projectUuid" uuid NOT NULL, "creatorUuid" uuid NOT NULL, CONSTRAINT "unique_mission_name_per_project" UNIQUE ("name", "projectUuid"), CONSTRAINT "PK_f5a9057579e3281d6a6d29d4165" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."file_event_type_enum" AS ENUM('CREATED', 'DELETED', 'UPLOAD_STARTED', 'UPLOAD_COMPLETED', 'TOPICS_EXTRACTED', 'FILE_CONVERTED', 'FILE_CONVERTED_FROM', 'FOXGLOVE_URL_GENERATED', 'DOWNLOADED', 'RENAMED', 'MOVED')`, + ); + await queryRunner.query( + `CREATE TABLE "file_event" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "type" "public"."file_event_type_enum" NOT NULL, "details" jsonb NOT NULL DEFAULT '{}', "filenameSnapshot" character varying NOT NULL, "actorUuid" uuid, "fileUuid" uuid, "missionUuid" uuid, "actionUuid" uuid, CONSTRAINT "PK_23cba1e0668c8a66d01ecb41174" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c6842eae6923ae6b85f30edcc0" ON "file_event" ("missionUuid") `, + ); + await queryRunner.query( + `CREATE TABLE "file_entity_categories_category" ("fileEntityUuid" uuid NOT NULL, "categoryUuid" uuid NOT NULL, CONSTRAINT "PK_d4ccff0045962eee4b5e017e82e" PRIMARY KEY ("fileEntityUuid", "categoryUuid"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_cc5fe2ad0324099241345de79c" ON "file_entity_categories_category" ("fileEntityUuid") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_11fa102cb9cd8159957e630e9c" ON "file_entity_categories_category" ("categoryUuid") `, + ); + await queryRunner.query( + `CREATE TABLE "tag_type_project_project" ("tagTypeUuid" uuid NOT NULL, "projectUuid" uuid NOT NULL, CONSTRAINT "PK_40fa37b8e026b9367654ed0d91a" PRIMARY KEY ("tagTypeUuid", "projectUuid"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_680533992be5f8a6d5664f1efa" ON "tag_type_project_project" ("tagTypeUuid") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_524b6f4291e81c3f9292f04439" ON "tag_type_project_project" ("projectUuid") `, + ); + await queryRunner.query( + `ALTER TABLE "apikey" ADD CONSTRAINT "FK_5dddd9b83adf747bf1806737513" FOREIGN KEY ("missionUuid") REFERENCES "mission"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "apikey" ADD CONSTRAINT "FK_f40e504202030871c12066e1473" FOREIGN KEY ("userUuid") REFERENCES "user"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "mission_access" ADD CONSTRAINT "FK_4c2f153a8894f810bcf56f400bb" FOREIGN KEY ("accessGroupUuid") REFERENCES "access_group"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "mission_access" ADD CONSTRAINT "FK_8bace5ae0a58b1ba00e65d19b2f" FOREIGN KEY ("missionUuid") REFERENCES "mission"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "project_access" ADD CONSTRAINT "FK_dc67b017e6be874a564bc3c0d1a" FOREIGN KEY ("accessGroupUuid") REFERENCES "access_group"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "project_access" ADD CONSTRAINT "FK_910e204772fdc68732f3728f28a" FOREIGN KEY ("projectUuid") REFERENCES "project"("uuid") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "access_group" ADD CONSTRAINT "FK_d7623d6ba5a2747e0accf801698" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "group_membership" ADD CONSTRAINT "FK_96fbddb1f06db780a4907d81def" FOREIGN KEY ("accessGroupUuid") REFERENCES "access_group"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "group_membership" ADD CONSTRAINT "FK_6b4d2fbf128cee85f97f082933e" FOREIGN KEY ("userUuid") REFERENCES "user"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "category" ADD CONSTRAINT "FK_6ec7022a4305bc7097017759d74" FOREIGN KEY ("projectUuid") REFERENCES "project"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "category" ADD CONSTRAINT "FK_b0ebe26a74adea2c452566606b5" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "topic" ADD CONSTRAINT "FK_fb4c54e905bdc7895aeba9618b9" FOREIGN KEY ("fileUuid") REFERENCES "file_entity"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity" ADD CONSTRAINT "FK_cff24fb6f37558b50778134d281" FOREIGN KEY ("missionUuid") REFERENCES "mission"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity" ADD CONSTRAINT "FK_2a601a65aeaa1d39169e2c1b91c" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity" ADD CONSTRAINT "FK_95ecf2a5d930a10916273fbd125" FOREIGN KEY ("parentUuid") REFERENCES "file_entity"("uuid") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ingestion_job" ADD CONSTRAINT "FK_f1ba404e1b48afbafbf54cce023" FOREIGN KEY ("missionUuid") REFERENCES "mission"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ingestion_job" ADD CONSTRAINT "FK_b9f36e5a7ac9a5558c979d98df7" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ingestion_job" ADD CONSTRAINT "FK_8d8fbf807f561c2679e584877e1" FOREIGN KEY ("file_uuid") REFERENCES "file_entity"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "project" ADD CONSTRAINT "FK_3efb531ce858475d93ca2c8d8f7" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "tag" ADD CONSTRAINT "FK_be048a4c67f6daafd6735543c18" FOREIGN KEY ("missionUuid") REFERENCES "mission"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "tag" ADD CONSTRAINT "FK_5d69bfb6df54eb228617b29afda" FOREIGN KEY ("tagTypeUuid") REFERENCES "tag_type"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "tag" ADD CONSTRAINT "FK_9857972d0bb698a79a9a9e1497f" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "user" ADD CONSTRAINT "FK_86922df84877163a10338c04b98" FOREIGN KEY ("account_uuid") REFERENCES "account"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "action_template" ADD CONSTRAINT "FK_2c6ce32d7ef9690cd202506ddff" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "action" ADD CONSTRAINT "FK_65d5fd46d2baf1d1d04aed18fc1" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "action" ADD CONSTRAINT "FK_44d0bfccde4ea3a017dfde75aa8" FOREIGN KEY ("missionUuid") REFERENCES "mission"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "action" ADD CONSTRAINT "FK_15c15b200913cf5234c0668cea0" FOREIGN KEY ("keyUuid") REFERENCES "apikey"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "action" ADD CONSTRAINT "FK_d5a3c19901c8aefc82ddd4edae6" FOREIGN KEY ("templateUuid") REFERENCES "action_template"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "action" ADD CONSTRAINT "FK_8add324fcb3c33f895a48ad2f5f" FOREIGN KEY ("workerUuid") REFERENCES "worker"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "mission" ADD CONSTRAINT "FK_6d71c0142267fbeaba6cae0e5db" FOREIGN KEY ("projectUuid") REFERENCES "project"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "mission" ADD CONSTRAINT "FK_09a47a85ae3cc20dd215278c93e" FOREIGN KEY ("creatorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "file_event" ADD CONSTRAINT "FK_752903e26f3ab3df2bb241ff272" FOREIGN KEY ("actorUuid") REFERENCES "user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "file_event" ADD CONSTRAINT "FK_fbb38fb11ab9e75a08367572498" FOREIGN KEY ("fileUuid") REFERENCES "file_entity"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "file_event" ADD CONSTRAINT "FK_c6842eae6923ae6b85f30edcc09" FOREIGN KEY ("missionUuid") REFERENCES "mission"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "file_event" ADD CONSTRAINT "FK_2238e243c3519db9e8931704b23" FOREIGN KEY ("actionUuid") REFERENCES "action"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity_categories_category" ADD CONSTRAINT "FK_cc5fe2ad0324099241345de79c9" FOREIGN KEY ("fileEntityUuid") REFERENCES "file_entity"("uuid") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity_categories_category" ADD CONSTRAINT "FK_11fa102cb9cd8159957e630e9c6" FOREIGN KEY ("categoryUuid") REFERENCES "category"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "tag_type_project_project" ADD CONSTRAINT "FK_680533992be5f8a6d5664f1efa0" FOREIGN KEY ("tagTypeUuid") REFERENCES "tag_type"("uuid") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "tag_type_project_project" ADD CONSTRAINT "FK_524b6f4291e81c3f9292f044399" FOREIGN KEY ("projectUuid") REFERENCES "project"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `CREATE VIEW "mission_access_view_entity" AS SELECT "mission"."uuid" as missionUUID, "user"."uuid" as userUUID, MAX("missionAccesses"."rights") as rights FROM "mission" "mission" INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid"="mission"."uuid" AND ("missionAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="missionAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ("memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "mission"."deletedAt" IS NULL GROUP BY "mission"."uuid", "user"."uuid"`, + ); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'mission_access_view_entity', + 'SELECT "mission"."uuid" as missionUUID, "user"."uuid" as userUUID, MAX("missionAccesses"."rights") as rights FROM "mission" "mission" INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid"="mission"."uuid" AND ("missionAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="missionAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ("memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "mission"."deletedAt" IS NULL GROUP BY "mission"."uuid", "user"."uuid"', + ], + ); + await queryRunner.query( + `CREATE VIEW "project_access_view_entity" AS SELECT "project"."uuid" as projectUUID, "user"."uuid" as userUUID, MAX("projectAccesses"."rights") as rights FROM "project" "project" INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid"="project"."uuid" AND ("projectAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="projectAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ( "memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW() AND "memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "project"."deletedAt" IS NULL GROUP BY "project"."uuid", "user"."uuid"`, + ); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'project_access_view_entity', + 'SELECT "project"."uuid" as projectUUID, "user"."uuid" as userUUID, MAX("projectAccesses"."rights") as rights FROM "project" "project" INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid"="project"."uuid" AND ("projectAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="projectAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ( "memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW() AND "memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "project"."deletedAt" IS NULL GROUP BY "project"."uuid", "user"."uuid"', + ], + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'project_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "project_access_view_entity"`); + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'mission_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "mission_access_view_entity"`); + await queryRunner.query( + `ALTER TABLE "tag_type_project_project" DROP CONSTRAINT "FK_524b6f4291e81c3f9292f044399"`, + ); + await queryRunner.query( + `ALTER TABLE "tag_type_project_project" DROP CONSTRAINT "FK_680533992be5f8a6d5664f1efa0"`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity_categories_category" DROP CONSTRAINT "FK_11fa102cb9cd8159957e630e9c6"`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity_categories_category" DROP CONSTRAINT "FK_cc5fe2ad0324099241345de79c9"`, + ); + await queryRunner.query( + `ALTER TABLE "file_event" DROP CONSTRAINT "FK_2238e243c3519db9e8931704b23"`, + ); + await queryRunner.query( + `ALTER TABLE "file_event" DROP CONSTRAINT "FK_c6842eae6923ae6b85f30edcc09"`, + ); + await queryRunner.query( + `ALTER TABLE "file_event" DROP CONSTRAINT "FK_fbb38fb11ab9e75a08367572498"`, + ); + await queryRunner.query( + `ALTER TABLE "file_event" DROP CONSTRAINT "FK_752903e26f3ab3df2bb241ff272"`, + ); + await queryRunner.query( + `ALTER TABLE "mission" DROP CONSTRAINT "FK_09a47a85ae3cc20dd215278c93e"`, + ); + await queryRunner.query( + `ALTER TABLE "mission" DROP CONSTRAINT "FK_6d71c0142267fbeaba6cae0e5db"`, + ); + await queryRunner.query( + `ALTER TABLE "action" DROP CONSTRAINT "FK_8add324fcb3c33f895a48ad2f5f"`, + ); + await queryRunner.query( + `ALTER TABLE "action" DROP CONSTRAINT "FK_d5a3c19901c8aefc82ddd4edae6"`, + ); + await queryRunner.query( + `ALTER TABLE "action" DROP CONSTRAINT "FK_15c15b200913cf5234c0668cea0"`, + ); + await queryRunner.query( + `ALTER TABLE "action" DROP CONSTRAINT "FK_44d0bfccde4ea3a017dfde75aa8"`, + ); + await queryRunner.query( + `ALTER TABLE "action" DROP CONSTRAINT "FK_65d5fd46d2baf1d1d04aed18fc1"`, + ); + await queryRunner.query( + `ALTER TABLE "action_template" DROP CONSTRAINT "FK_2c6ce32d7ef9690cd202506ddff"`, + ); + await queryRunner.query( + `ALTER TABLE "user" DROP CONSTRAINT "FK_86922df84877163a10338c04b98"`, + ); + await queryRunner.query( + `ALTER TABLE "tag" DROP CONSTRAINT "FK_9857972d0bb698a79a9a9e1497f"`, + ); + await queryRunner.query( + `ALTER TABLE "tag" DROP CONSTRAINT "FK_5d69bfb6df54eb228617b29afda"`, + ); + await queryRunner.query( + `ALTER TABLE "tag" DROP CONSTRAINT "FK_be048a4c67f6daafd6735543c18"`, + ); + await queryRunner.query( + `ALTER TABLE "project" DROP CONSTRAINT "FK_3efb531ce858475d93ca2c8d8f7"`, + ); + await queryRunner.query( + `ALTER TABLE "ingestion_job" DROP CONSTRAINT "FK_8d8fbf807f561c2679e584877e1"`, + ); + await queryRunner.query( + `ALTER TABLE "ingestion_job" DROP CONSTRAINT "FK_b9f36e5a7ac9a5558c979d98df7"`, + ); + await queryRunner.query( + `ALTER TABLE "ingestion_job" DROP CONSTRAINT "FK_f1ba404e1b48afbafbf54cce023"`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity" DROP CONSTRAINT "FK_95ecf2a5d930a10916273fbd125"`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity" DROP CONSTRAINT "FK_2a601a65aeaa1d39169e2c1b91c"`, + ); + await queryRunner.query( + `ALTER TABLE "file_entity" DROP CONSTRAINT "FK_cff24fb6f37558b50778134d281"`, + ); + await queryRunner.query( + `ALTER TABLE "topic" DROP CONSTRAINT "FK_fb4c54e905bdc7895aeba9618b9"`, + ); + await queryRunner.query( + `ALTER TABLE "category" DROP CONSTRAINT "FK_b0ebe26a74adea2c452566606b5"`, + ); + await queryRunner.query( + `ALTER TABLE "category" DROP CONSTRAINT "FK_6ec7022a4305bc7097017759d74"`, + ); + await queryRunner.query( + `ALTER TABLE "group_membership" DROP CONSTRAINT "FK_6b4d2fbf128cee85f97f082933e"`, + ); + await queryRunner.query( + `ALTER TABLE "group_membership" DROP CONSTRAINT "FK_96fbddb1f06db780a4907d81def"`, + ); + await queryRunner.query( + `ALTER TABLE "access_group" DROP CONSTRAINT "FK_d7623d6ba5a2747e0accf801698"`, + ); + await queryRunner.query( + `ALTER TABLE "project_access" DROP CONSTRAINT "FK_910e204772fdc68732f3728f28a"`, + ); + await queryRunner.query( + `ALTER TABLE "project_access" DROP CONSTRAINT "FK_dc67b017e6be874a564bc3c0d1a"`, + ); + await queryRunner.query( + `ALTER TABLE "mission_access" DROP CONSTRAINT "FK_8bace5ae0a58b1ba00e65d19b2f"`, + ); + await queryRunner.query( + `ALTER TABLE "mission_access" DROP CONSTRAINT "FK_4c2f153a8894f810bcf56f400bb"`, + ); + await queryRunner.query( + `ALTER TABLE "apikey" DROP CONSTRAINT "FK_f40e504202030871c12066e1473"`, + ); + await queryRunner.query( + `ALTER TABLE "apikey" DROP CONSTRAINT "FK_5dddd9b83adf747bf1806737513"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_524b6f4291e81c3f9292f04439"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_680533992be5f8a6d5664f1efa"`, + ); + await queryRunner.query(`DROP TABLE "tag_type_project_project"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_11fa102cb9cd8159957e630e9c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_cc5fe2ad0324099241345de79c"`, + ); + await queryRunner.query(`DROP TABLE "file_entity_categories_category"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_c6842eae6923ae6b85f30edcc0"`, + ); + await queryRunner.query(`DROP TABLE "file_event"`); + await queryRunner.query(`DROP TYPE "public"."file_event_type_enum"`); + await queryRunner.query(`DROP TABLE "mission"`); + await queryRunner.query(`DROP TABLE "action"`); + await queryRunner.query(`DROP TYPE "public"."action_artifacts_enum"`); + await queryRunner.query(`DROP TYPE "public"."action_state_enum"`); + await queryRunner.query(`DROP TABLE "worker"`); + await queryRunner.query(`DROP TABLE "action_template"`); + await queryRunner.query( + `DROP TYPE "public"."action_template_accessrights_enum"`, + ); + await queryRunner.query(`DROP TABLE "user"`); + await queryRunner.query(`DROP TYPE "public"."user_role_enum"`); + await queryRunner.query(`DROP TABLE "tag"`); + await queryRunner.query(`DROP TABLE "tag_type"`); + await queryRunner.query(`DROP TYPE "public"."tag_type_datatype_enum"`); + await queryRunner.query(`DROP TABLE "project"`); + await queryRunner.query(`DROP TABLE "ingestion_job"`); + await queryRunner.query( + `DROP TYPE "public"."ingestion_job_location_enum"`, + ); + await queryRunner.query( + `DROP TYPE "public"."ingestion_job_state_enum"`, + ); + await queryRunner.query(`DROP TABLE "file_entity"`); + await queryRunner.query(`DROP TYPE "public"."file_entity_origin_enum"`); + await queryRunner.query(`DROP TYPE "public"."file_entity_state_enum"`); + await queryRunner.query(`DROP TYPE "public"."file_entity_type_enum"`); + await queryRunner.query(`DROP TABLE "topic"`); + await queryRunner.query(`DROP TABLE "category"`); + await queryRunner.query(`DROP TABLE "group_membership"`); + await queryRunner.query(`DROP TABLE "access_group"`); + await queryRunner.query(`DROP TYPE "public"."access_group_type_enum"`); + await queryRunner.query(`DROP TABLE "project_access"`); + await queryRunner.query( + `DROP TYPE "public"."project_access_rights_enum"`, + ); + await queryRunner.query(`DROP TABLE "mission_access"`); + await queryRunner.query( + `DROP TYPE "public"."mission_access_rights_enum"`, + ); + await queryRunner.query(`DROP TABLE "apikey"`); + await queryRunner.query(`DROP TYPE "public"."apikey_rights_enum"`); + await queryRunner.query(`DROP TYPE "public"."apikey_key_type_enum"`); + await queryRunner.query(`DROP TABLE "account"`); + await queryRunner.query(`DROP TYPE "public"."account_provider_enum"`); + await queryRunner.query(`DROP TABLE "base_entity"`); + } +} diff --git a/backend/migration/migrations/1765363646576-migration-docker-refactor.ts b/backend/migration/migrations/1765363646576-migration-docker-refactor.ts new file mode 100644 index 000000000..cf304b511 --- /dev/null +++ b/backend/migration/migrations/1765363646576-migration-docker-refactor.ts @@ -0,0 +1,97 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MigrationDockerRefactor1765363646576 implements MigrationInterface { + name = 'MigrationDockerRefactor1765363646576'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'project_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "project_access_view_entity"`); + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'mission_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "mission_access_view_entity"`); + await queryRunner.query(`CREATE VIEW "project_access_view_entity" AS + SELECT + "project"."uuid" AS "projectuuid", + "user"."uuid" AS "useruuid", + MAX("projectAccesses"."rights") AS "rights" + FROM "project" "project" + INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid" = "project"."uuid" + INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid" = "projectAccesses"."accessGroupUuid" + INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid" = "accessGroup"."uuid" AND ("memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW()) + INNER JOIN "user" "user" ON "user"."uuid" = "memberships"."userUuid" + GROUP BY "project"."uuid", "user"."uuid" + `); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'project_access_view_entity', + 'SELECT\n "project"."uuid" AS "projectuuid",\n "user"."uuid" AS "useruuid",\n MAX("projectAccesses"."rights") AS "rights"\n FROM "project" "project"\n INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid" = "project"."uuid"\n INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid" = "projectAccesses"."accessGroupUuid"\n INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid" = "accessGroup"."uuid" AND ("memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW())\n INNER JOIN "user" "user" ON "user"."uuid" = "memberships"."userUuid"\n GROUP BY "project"."uuid", "user"."uuid"', + ], + ); + await queryRunner.query(`CREATE VIEW "mission_access_view_entity" AS + SELECT + "mission"."uuid" AS "missionuuid", + "user"."uuid" AS "useruuid", + MAX("missionAccesses"."rights") AS "rights" + FROM "mission" "mission" + INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid" = "mission"."uuid" + INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid" = "missionAccesses"."accessGroupUuid" + INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid" = "accessGroup"."uuid" + INNER JOIN "user" "user" ON "user"."uuid" = "memberships"."userUuid" + GROUP BY "mission"."uuid", "user"."uuid" + `); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'mission_access_view_entity', + 'SELECT\n "mission"."uuid" AS "missionuuid",\n "user"."uuid" AS "useruuid",\n MAX("missionAccesses"."rights") AS "rights"\n FROM "mission" "mission"\n INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid" = "mission"."uuid"\n INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid" = "missionAccesses"."accessGroupUuid"\n INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid" = "accessGroup"."uuid"\n INNER JOIN "user" "user" ON "user"."uuid" = "memberships"."userUuid"\n GROUP BY "mission"."uuid", "user"."uuid"', + ], + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'mission_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "mission_access_view_entity"`); + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'project_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "project_access_view_entity"`); + await queryRunner.query( + `CREATE VIEW "mission_access_view_entity" AS SELECT "mission"."uuid" as missionUUID, "user"."uuid" as userUUID, MAX("missionAccesses"."rights") as rights FROM "mission" "mission" INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid"="mission"."uuid" AND ("missionAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="missionAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ("memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "mission"."deletedAt" IS NULL GROUP BY "mission"."uuid", "user"."uuid"`, + ); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'mission_access_view_entity', + 'SELECT "mission"."uuid" as missionUUID, "user"."uuid" as userUUID, MAX("missionAccesses"."rights") as rights FROM "mission" "mission" INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid"="mission"."uuid" AND ("missionAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="missionAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ("memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "mission"."deletedAt" IS NULL GROUP BY "mission"."uuid", "user"."uuid"', + ], + ); + await queryRunner.query( + `CREATE VIEW "project_access_view_entity" AS SELECT "project"."uuid" as projectUUID, "user"."uuid" as userUUID, MAX("projectAccesses"."rights") as rights FROM "project" "project" INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid"="project"."uuid" AND ("projectAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="projectAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ( "memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW() AND "memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "project"."deletedAt" IS NULL GROUP BY "project"."uuid", "user"."uuid"`, + ); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'project_access_view_entity', + 'SELECT "project"."uuid" as projectUUID, "user"."uuid" as userUUID, MAX("projectAccesses"."rights") as rights FROM "project" "project" INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid"="project"."uuid" AND ("projectAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="projectAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ( "memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW() AND "memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "project"."deletedAt" IS NULL GROUP BY "project"."uuid", "user"."uuid"', + ], + ); + } +} diff --git a/backend/migration/migrations/1765801458354-sync-views.ts b/backend/migration/migrations/1765801458354-sync-views.ts new file mode 100644 index 000000000..5c62addf0 --- /dev/null +++ b/backend/migration/migrations/1765801458354-sync-views.ts @@ -0,0 +1,97 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SyncViews1765801458354 implements MigrationInterface { + name = 'SyncViews1765801458354'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'project_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "project_access_view_entity"`); + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'mission_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "mission_access_view_entity"`); + await queryRunner.query(`CREATE VIEW "project_access_view_entity" AS + SELECT + "project"."uuid" AS "projectuuid", + "user"."uuid" AS "useruuid", + MAX("projectAccesses"."rights") AS "rights" + FROM "project" "project" + INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid" = "project"."uuid" + INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid" = "projectAccesses"."accessGroupUuid" + INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid" = "accessGroup"."uuid" AND ("memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW()) + INNER JOIN "user" "user" ON "user"."uuid" = "memberships"."userUuid" + GROUP BY "project"."uuid", "user"."uuid" + `); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'project_access_view_entity', + 'SELECT\n "project"."uuid" AS "projectuuid",\n "user"."uuid" AS "useruuid",\n MAX("projectAccesses"."rights") AS "rights"\n FROM "project" "project"\n INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid" = "project"."uuid"\n INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid" = "projectAccesses"."accessGroupUuid"\n INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid" = "accessGroup"."uuid" AND ("memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW())\n INNER JOIN "user" "user" ON "user"."uuid" = "memberships"."userUuid"\n GROUP BY "project"."uuid", "user"."uuid"', + ], + ); + await queryRunner.query(`CREATE VIEW "mission_access_view_entity" AS + SELECT + "mission"."uuid" AS "missionuuid", + "user"."uuid" AS "useruuid", + MAX("missionAccesses"."rights") AS "rights" + FROM "mission" "mission" + INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid" = "mission"."uuid" + INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid" = "missionAccesses"."accessGroupUuid" + INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid" = "accessGroup"."uuid" + INNER JOIN "user" "user" ON "user"."uuid" = "memberships"."userUuid" + GROUP BY "mission"."uuid", "user"."uuid" + `); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'mission_access_view_entity', + 'SELECT\n "mission"."uuid" AS "missionuuid",\n "user"."uuid" AS "useruuid",\n MAX("missionAccesses"."rights") AS "rights"\n FROM "mission" "mission"\n INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid" = "mission"."uuid"\n INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid" = "missionAccesses"."accessGroupUuid"\n INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid" = "accessGroup"."uuid"\n INNER JOIN "user" "user" ON "user"."uuid" = "memberships"."userUuid"\n GROUP BY "mission"."uuid", "user"."uuid"', + ], + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'mission_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "mission_access_view_entity"`); + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['VIEW', 'project_access_view_entity', 'public'], + ); + await queryRunner.query(`DROP VIEW "project_access_view_entity"`); + await queryRunner.query( + `CREATE VIEW "mission_access_view_entity" AS SELECT "mission"."uuid" as missionUUID, "user"."uuid" as userUUID, MAX("missionAccesses"."rights") as rights FROM "mission" "mission" INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid"="mission"."uuid" AND ("missionAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="missionAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ("memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "mission"."deletedAt" IS NULL GROUP BY "mission"."uuid", "user"."uuid"`, + ); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'mission_access_view_entity', + 'SELECT "mission"."uuid" as missionUUID, "user"."uuid" as userUUID, MAX("missionAccesses"."rights") as rights FROM "mission" "mission" INNER JOIN "mission_access" "missionAccesses" ON "missionAccesses"."missionUuid"="mission"."uuid" AND ("missionAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="missionAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ("memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "mission"."deletedAt" IS NULL GROUP BY "mission"."uuid", "user"."uuid"', + ], + ); + await queryRunner.query( + `CREATE VIEW "project_access_view_entity" AS SELECT "project"."uuid" as projectUUID, "user"."uuid" as userUUID, MAX("projectAccesses"."rights") as rights FROM "project" "project" INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid"="project"."uuid" AND ("projectAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="projectAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ( "memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW() AND "memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "project"."deletedAt" IS NULL GROUP BY "project"."uuid", "user"."uuid"`, + ); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'VIEW', + 'project_access_view_entity', + 'SELECT "project"."uuid" as projectUUID, "user"."uuid" as userUUID, MAX("projectAccesses"."rights") as rights FROM "project" "project" INNER JOIN "project_access" "projectAccesses" ON "projectAccesses"."projectUuid"="project"."uuid" AND ("projectAccesses"."deletedAt" IS NULL) INNER JOIN "access_group" "accessGroup" ON "accessGroup"."uuid"="projectAccesses"."accessGroupUuid" AND ("accessGroup"."deletedAt" IS NULL) INNER JOIN "group_membership" "memberships" ON "memberships"."accessGroupUuid"="accessGroup"."uuid" AND ( "memberships"."expirationDate" IS NULL OR "memberships"."expirationDate" > NOW() AND "memberships"."deletedAt" IS NULL) INNER JOIN "user" "user" ON "user"."uuid"="memberships"."userUuid" AND ("user"."deletedAt" IS NULL) WHERE "project"."deletedAt" IS NULL GROUP BY "project"."uuid", "user"."uuid"', + ], + ); + } +} diff --git a/backend/migration/prod/migration.config.ts b/backend/migration/prod/migration.config.ts index 782b653a8..f0c4f0887 100644 --- a/backend/migration/prod/migration.config.ts +++ b/backend/migration/prod/migration.config.ts @@ -1,3 +1,4 @@ +import 'tsconfig-paths/register'; import { DataSource } from 'typeorm'; import { getConfig } from './production-datasource.config'; @@ -5,6 +6,7 @@ const datasource = new DataSource(getConfig()); datasource .initialize() + // eslint-disable-next-line no-console .then(console.log) // eslint-disable-next-line unicorn/prefer-top-level-await .catch((error: unknown) => { diff --git a/backend/migration/prod/migrations/.gitkeep b/backend/migration/prod/migrations/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/migration/prod/production-datasource.config.ts b/backend/migration/prod/production-datasource.config.ts index 978b08eb4..455d330a6 100644 --- a/backend/migration/prod/production-datasource.config.ts +++ b/backend/migration/prod/production-datasource.config.ts @@ -4,22 +4,22 @@ import { DataSourceOptions } from 'typeorm'; dotenv.config({ path: './migration/.env' }); -const databasePort = process.env['prod_port']; +const databasePort = process.env.prod_port; export function getConfig() { return { type: 'postgres', - host: process.env['prod_dbhost'], + host: process.env.prod_dbhost, port: Number.parseInt(databasePort ?? '5432', 10), - ssl: process.env['prod_ssl'] === 'true', - username: process.env['prod_dbuser'], - password: process.env['prod_dbpassword'], - database: process.env['prod_dbname'], + ssl: process.env.prod_ssl === 'true', + username: process.env.prod_dbuser, + password: process.env.prod_dbpassword, + database: process.env.prod_dbname, synchronize: false, - migrations: ['migration/prod/migrations/*.ts'], + migrations: ['migration/migrations/*.ts'], entities: [ - '../common/entities/**/*.entity{.ts,.js}', - '../common/viewEntities/**/*.entity{.ts,.js}', + '../packages/backend-common/src/entities/**/*.entity{.ts,.js}', + '../packages/backend-common/src/viewEntities/**/*.entity{.ts,.js}', ], } as DataSourceOptions; } diff --git a/backend/migration/scripts/generate-clean-migration.sh b/backend/migration/scripts/generate-clean-migration.sh new file mode 100755 index 000000000..42cfbc0c3 --- /dev/null +++ b/backend/migration/scripts/generate-clean-migration.sh @@ -0,0 +1,87 @@ +#!/bin/bash +set -e + +CHECK_MODE=false +if [ "$1" == "--check" ]; then + CHECK_MODE=true + MIGRATION_NAME="ci_check_$(date +%s)" +else + MIGRATION_NAME=$1 +fi + +if [ -z "$MIGRATION_NAME" ]; then + echo "Error: Migration name is required." + echo "Usage: pnpm run migration:generate:clean " + echo " pnpm run migration:check" + exit 1 +fi + +echo "🚀 Starting clean migration generation for '$MIGRATION_NAME'..." + +# 1. Start a temporary Postgres container +CONTAINER_NAME="temp-migration-db-$(date +%s)" +echo "📦 Starting temporary Postgres container: $CONTAINER_NAME" +# Use -p 0:5432 to let Docker assign a random ephemeral port +docker run --name "$CONTAINER_NAME" -e POSTGRES_PASSWORD=temp_password -p 0:5432 -d postgres:17 > /dev/null + +# Ensure cleanup happens on exit +cleanup() { + echo "🧹 Cleaning up..." + docker rm -f "$CONTAINER_NAME" > /dev/null +} +trap cleanup EXIT + +# 2. Wait for DB to be ready +echo "⏳ Waiting for database to be ready..." +sleep 5 + +# Get the assigned random port +DB_PORT=$(docker port "$CONTAINER_NAME" 5432 | awk -F: '{print $2}') +echo "🔌 Temporary DB running on port: $DB_PORT" + +# 3. Export connection details +export dev_dbhost=localhost +export dev_port=$DB_PORT +export dev_dbuser=postgres +export dev_dbpassword=temp_password +export dev_dbname=postgres +export dev_ssl=false + +# 4. Run existing migrations +echo "🔄 Running existing migrations..." +pnpm run typeorm migration:run -d migration/dev/migration.config.ts > /dev/null + +# 5. Generate the new migration +# 5. Generate the new migration +echo "📝 Generating new migration..." +set +e +OUTPUT=$(pnpm run typeorm migration:generate "migration/migrations/$MIGRATION_NAME" -d migration/dev/migration.config.ts 2>&1) +EXIT_CODE=$? +set -e + +if [ $EXIT_CODE -ne 0 ]; then + if echo "$OUTPUT" | grep -q "No changes in database schema were found"; then + echo "⚠️ No changes in database schema were found." + if [ "$CHECK_MODE" = true ]; then + echo "✅ Check passed: Migrations are up to date." + exit 0 + else + echo "No migration generated." + exit 1 + fi + else + echo "❌ Migration generation failed:" + echo "$OUTPUT" + exit $EXIT_CODE + fi +else + if [ "$CHECK_MODE" = true ]; then + echo "❌ Check failed: A new migration was generated." + echo "This means the committed migrations are not in sync with the entities." + echo "Please run 'pnpm run migration:generate:clean ' and commit the result." + exit 1 + else + echo "$OUTPUT" + echo "✅ Migration generation complete!" + fi +fi diff --git a/backend/nest-cli.json b/backend/nest-cli.json index 30eb0757c..fce156346 100644 --- a/backend/nest-cli.json +++ b/backend/nest-cli.json @@ -3,7 +3,9 @@ "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { - "deleteOutDir": true + "deleteOutDir": true, + "webpack": true, + "webpackPath": "./webpack.config.js" }, - "entryFile": "backend/src/main" + "entryFile": "main" } diff --git a/backend/package.json b/backend/package.json index 220a1956b..ec78fccb5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,25 +1,31 @@ { "name": "kleinkram-backend", - "version": "0.55.2", + "version": "0.56.0", "description": "", "author": "", - "private": true, "license": "MIT", "scripts": { - "build": "nest build", - "format": "prettier --write \\\"src/**/*.ts\\\" \\\"tests/**/*.ts\\\"", - "start": "nest start", - "start:dev": "nest start --watch --preserveWatchOutput", - "start:debug": "nest start --debug --watch", - "start:prod": "node dist/backend/src/main.js", + "build": "nest build --webpack", + "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"", + "start": "nest start --webpack", + "start:dev": "nest start --webpack --watch --preserveWatchOutput", + "start:debug": "nest start --webpack --debug --watch", + "start:prod": "node dist/main.js", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest --runInBand --detectOpenHandles --forceExit", "test:verbose": "jest --silent=false", - "typeorm": "typeorm-ts-node-commonjs" + "typeorm": "typeorm-ts-node-commonjs", + "migration:generate:clean": "./migration/scripts/generate-clean-migration.sh", + "migration:check": "./migration/scripts/generate-clean-migration.sh --check", + "generate-openapi": "node -r ts-node/register ./scripts/generate-openapi.ts", + "gen:docs": "node -r ts-node/register -r tsconfig-paths/register ./scripts/generate-openapi.ts" }, "dependencies": { - "@aws-sdk/client-sts": "3.726.1", - "@google-cloud/local-auth": "^3.0.1", + "@aws-sdk/client-sts": "3.939.0", + "@kleinkram/api-dto": "workspace:*", + "@kleinkram/backend-common": "workspace:*", + "@kleinkram/shared": "workspace:*", + "@kleinkram/validation": "workspace:*", "@nestjs/common": "^10.4.20", "@nestjs/config": "^4.0.2", "@nestjs/core": "^10.4.5", @@ -37,12 +43,10 @@ "@opentelemetry/instrumentation-fetch": "^0.208.0", "@opentelemetry/instrumentation-http": "^0.208.0", "@opentelemetry/instrumentation-nestjs-core": "^0.55.0", - "@opentelemetry/instrumentation-pg": "^0.61.0", + "@opentelemetry/instrumentation-pg": "^0.61.1", "@opentelemetry/instrumentation-winston": "^0.53.0", "@opentelemetry/sdk-node": "^0.208.0", "@opentelemetry/sdk-trace-base": "^2.0.0", - "@swc/core": "^1.13.5", - "@swc/jest": "^0.2.39", "@willsoto/nestjs-prometheus": "^6.0.2", "aws4": "^1.13.2", "axios": "^1.13.2", @@ -51,9 +55,11 @@ "class-validator": "^0.14.2", "cookie-parser": "^1.4.6", "date-fns": "^4.1.0", + "dotenv": "^17.2.3", + "express": "^4.21.2", "form-data": "^4.0.4", - "googleapis": "^165.0.0", "jsonwebtoken": "^9.0.2", + "logform": "^2.7.0", "minio": "8.0.6", "multer": "^2.0.2", "passport": "^0.7.0", @@ -61,15 +67,13 @@ "passport-google-oauth": "^2.0.0", "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", + "passport-oauth2": "^1.8.0", "pg": "^8.16.3", "prom-client": "^15.1.3", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.2", "typeorm": "^0.3.27", "uuid": "^13.0.0", - "vue": "^3.5.21", - "vue-json-pretty": "^2.6.0", - "webpack": "^5.102.1", "winston": "^3.18.3", "winston-loki": "^6.0.7" }, @@ -79,28 +83,42 @@ "@nestjs/cli": "^11.0.10", "@nestjs/schematics": "^11.0.9", "@nestjs/testing": "^10.0.0", + "@swc/core": "^1.13.5", + "@swc/jest": "^0.2.39", "@types/cookie-parser": "^1.4.9", + "@types/dotenv": "^8.2.3", "@types/express": "^5.0.3", "@types/jest": "^29.5.2", + "@types/jsonwebtoken": "^9.0.10", "@types/multer": "^2.0.0", - "@types/node": "^24.9.1", - "@types/passport-github": "^1.1.12", + "@types/node": "^24.10.1", + "@types/passport-github": "^1.1.13", + "@types/passport-github2": "^1.2.0", "@types/passport-google-oauth20": "^2.0.16", + "@types/passport-jwt": "^3.0.0", + "@types/passport-oauth2": "^1.8.0", "@types/supertest": "^6.0.3", "@typescript-eslint/eslint-plugin": "^8.32.1", "@typescript-eslint/parser": "^8.25.0", + "concurrently": "^8.2.2", "eslint": "^9.37.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", "jest": "^29.7.0", "jest-junit": "^16.0.0", + "node-loader": "^2.0.0", "prettier": "^3.6.2", + "run-script-webpack-plugin": "^0.2.3", "source-map-support": "^0.5.21", "supertest": "^7.1.4", "ts-jest": "^29.4.4", - "ts-loader": "^9.4.3", + "ts-loader": "^9.5.4", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", - "typescript": "5.8.3" + "typescript": "5.8.3", + "vue": "^3.5.25", + "vue-json-pretty": "^2.6.0", + "webpack": "^5.102.1", + "webpack-node-externals": "^3.0.0" } } diff --git a/backend/prod.Dockerfile b/backend/prod.Dockerfile deleted file mode 100644 index 9a78fcf04..000000000 --- a/backend/prod.Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM node:24-alpine - -COPY ./backend/package.json /usr/src/app/backend/ -COPY ./backend/yarn.lock /usr/src/app/backend/ -COPY ./common/package.json /usr/src/app/common/ -COPY ./common/yarn.lock /usr/src/app/common/ - -WORKDIR /usr/src/app/backend -RUN yarn --immutable - -WORKDIR /usr/src/app/common -RUN yarn --immutable -RUN apk --no-cache add postgresql-client - -# copy the rest of the files -COPY ./backend /usr/src/app/backend -COPY ./common /usr/src/app/common - -# build the app -WORKDIR /usr/src/app/backend -RUN yarn run build -# marke the entrypoint.sh as executable -RUN chmod +x entrypoint.sh -CMD ["sh", "entrypoint.sh"] diff --git a/backend/scripts/generate-openapi.ts b/backend/scripts/generate-openapi.ts new file mode 100644 index 000000000..bfc368531 --- /dev/null +++ b/backend/scripts/generate-openapi.ts @@ -0,0 +1,267 @@ +import { INestApplication, Type } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { Test } from '@nestjs/testing'; +import * as fs from 'node:fs'; +import path from 'node:path'; +import 'reflect-metadata'; + +// Reuse constants from @nestjs/common if possible, or string literals +// PATH_METADATA 'path' +const PATH_METADATA = 'path'; + +function findControllers(directory: string, fileList: string[] = []): string[] { + const files = fs.readdirSync(directory); + for (const file of files) { + const filePath = path.join(directory, file); + const stat = fs.statSync(filePath); + if (stat.isDirectory()) { + findControllers(filePath, fileList); + } else { + if (file.endsWith('.controller.ts')) { + fileList.push(filePath); + } + } + } + return fileList; +} + +async function generateOpenApi() { + const outputDirectory = + process.argv[2] && !process.argv[2].startsWith('-') + ? path.resolve(process.argv[2]) + : process.cwd(); + + if (process.argv[2]) { + console.warn(`Writing output to: ${outputDirectory}`); + if (!fs.existsSync(outputDirectory)) { + fs.mkdirSync(outputDirectory, { recursive: true }); + } + } + + console.warn('Scanning for controllers...'); + // eslint-disable-next-line unicorn/prefer-module + const controllersDirectory = path.join(__dirname, '../src/endpoints'); + const controllerFiles = findControllers(controllersDirectory); + const controllers: Type[] = []; + + for (const file of controllerFiles) { + // Dynamic import + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const module = await import(file); + + // Find exported classes that look like controllers + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + for (const exported of Object.values(module)) { + if (typeof exported === 'function') { + // Check if it has @Controller decorator metadata + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const isController = Reflect.getMetadata( + PATH_METADATA, + + exported, + ); + if (isController !== undefined) { + console.warn( + `Found controller: ${String((exported as Type).name)}`, + ); + + controllers.push(exported as Type); + } + } + } + } + + if (controllers.length === 0) { + console.warn('No controllers found! Swagger will be empty.'); + } + + console.warn( + `Initializing NestJS TestingModule with ${String(controllers.length)} controllers...`, + ); + + const moduleFixture = await Test.createTestingModule({ + controllers, + providers: [], + }) + + .useMocker((_token) => { + // Create a recursive mock that handles any property access or method call + const recursiveMock = new Proxy( + {}, + { + get: (target, property) => { + if ( + property === 'then' || + property === 'catch' || + property === 'finally' + ) { + return; // Not a promise + } + if (property === 'toJSON') { + return () => ({}); + } + + // Return a function that returns the recursive mock (for method chaining) + // If properties are accessed (not called), it returns the function which is truthy/object-ish + + return (..._args: unknown[]) => recursiveMock; + }, + }, + ); + return recursiveMock; + }) + .compile(); + + const app = moduleFixture.createNestApplication(); + + const config = new DocumentBuilder() + .setTitle('API Documentation') + .setDescription('API description') + .setVersion('1.0') + .addBearerAuth() + .build(); + + const document = SwaggerModule.createDocument(app, config); + + fs.writeFileSync( + path.join(outputDirectory, 'swagger.json'), + JSON.stringify(document, null, 2), + ); + // eslint-disable-next-line no-console + console.log('swagger.json generated successfully.'); + + // Initialize the app to generate the router stack + await app.init(); + saveEndpointsAsJson( + app, + path.join(outputDirectory, '__generated__endpoints.json'), + ); + + // Generate api-modules.md + console.warn('Generating api-modules.md...'); + let mdContent = '| Module | Path | Description |\n| :--- | :--- | :--- |\n'; + + // Also generate a JSON file for the Vue component + const modules: { + name: string; + path: string; + link: string; + description: string; + }[] = []; + + // Sort controllers by path for better readability + const sortedControllers = controllers.sort((a, b) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + let pathA = Reflect.getMetadata(PATH_METADATA, a); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + let pathB = Reflect.getMetadata(PATH_METADATA, b); + + // Handle array of paths (take first) or undefined + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + if (Array.isArray(pathA)) pathA = pathA[0]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + if (Array.isArray(pathB)) pathB = pathB[0]; + + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + pathA = String(pathA || ''); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + pathB = String(pathB || ''); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + return pathA.localeCompare(pathB); + }); + + for (const controller of sortedControllers) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + let controllerPath = Reflect.getMetadata(PATH_METADATA, controller); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + if (Array.isArray(controllerPath)) controllerPath = controllerPath[0]; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + controllerPath = String(controllerPath || ''); + // Try to infer a nice name/link. + // We don't have the file path easily associated with the class here unless we kept it. + // But we can guess from the class name: AccessController -> access + const name = controller.name.replace('Controller', '').toLowerCase(); + + // Generate component file for this module + const moduleFileName = `${name}.md`; + const moduleTitle = name.charAt(0).toUpperCase() + name.slice(1); + const moduleContent = `--- +aside: false +--- + +# ${moduleTitle} Module + +// eslint-disable-next-line @typescript-eslint/restrict-template-expressions + +`; + fs.writeFileSync( + path.join(outputDirectory, moduleFileName), + moduleContent, + ); + + // Check if we can link to a file. We assume the doc file exists and is named {name}.md + // We can't easily check file existence in docs/ from here without path traversing, + // but broadly we can assume standard naming convention. + const link = `[\`${name}\`](${name}.md)`; + + mdContent += `| ${link} | \`/${String(controllerPath)}\` | Docs for ${name} module |\n`; + + modules.push({ + name: name, + path: `/${String(controllerPath)}`, + link: `generated/${name}.html`, + description: `Docs for ${name} module`, + }); + } + + fs.writeFileSync(path.join(outputDirectory, 'api-modules.md'), mdContent); + fs.writeFileSync( + path.join(outputDirectory, 'api-modules.json'), + JSON.stringify(modules, null, 2), + ); + console.warn( + 'api-modules.md, api-modules.json and individual module files generated successfully.', + ); + + await app.close(); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(0); +} + +function saveEndpointsAsJson(app: INestApplication, filename: string): void { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const server = app.getHttpServer(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + const endpoints = server._events.request._router.stack + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access + .filter((r: any) => r.route) + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + .map((r: any) => ({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + url: r.route.path, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + method: r.route.stack[0].method, + })); + + try { + fs.writeFileSync(filename, JSON.stringify(endpoints, null, 2)); + console.warn(`Endpoints saved to ${filename}`); + } catch (error) { + console.warn( + `Failed to save endpoints to ${filename}: ${String(error)}`, + ); + } +} + +// eslint-disable-next-line unicorn/prefer-top-level-await +generateOpenApi().catch((error: unknown) => { + console.error(error); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); +}); diff --git a/backend/access_config.json b/backend/src/access_config.json similarity index 73% rename from backend/access_config.json rename to backend/src/access_config.json index 00cdb58e1..b179fec55 100644 --- a/backend/access_config.json +++ b/backend/src/access_config.json @@ -1,13 +1,13 @@ { "emails": [ { - "email": "kleinkram.leggedrobotics.com", + "email": "kleinkram.dev", "access_groups": ["00000000-0000-0000-0000-000000000000"] } ], "access_groups": [ { - "name": "Leggedrobotics", + "name": "Kleinkram Developers", "uuid": "00000000-0000-0000-0000-000000000000", "rights": 10 } diff --git a/backend/src/app-version.ts b/backend/src/app-version.ts index 5f9f59439..97bc87ee5 100644 --- a/backend/src/app-version.ts +++ b/backend/src/app-version.ts @@ -7,7 +7,6 @@ interface PackageJson { } const path = '/usr/src/app/backend/package.json'; -const fallbackPath = '../package.json'; function readFileIfExists(filePath: string): PackageJson | null { try { @@ -25,8 +24,9 @@ function readFileIfExists(filePath: string): PackageJson | null { export const appVersion = (() => { let packageJson = readFileIfExists(path); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (!packageJson) { - packageJson = readFileIfExists(fallbackPath); + packageJson = readFileIfExists('./package.json'); } return packageJson?.version ?? ''; diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index ae48334b1..e68fe415a 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,6 +1,7 @@ -import env from '@common/environment'; -import { StorageModule } from '@common/modules/storage/storage.module'; -import configuration from '@common/typeorm-config'; +import env from '@kleinkram/backend-common/environment'; +import { StorageModule } from '@kleinkram/backend-common/modules/storage/storage.module'; +import configuration from '@kleinkram/backend-common/typeorm-config'; +import { AccessGroupConfig } from '@kleinkram/shared'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { PassportModule } from '@nestjs/passport'; @@ -8,16 +9,19 @@ import { ScheduleModule } from '@nestjs/schedule'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PrometheusModule } from '@willsoto/nestjs-prometheus'; import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; -import accessConfig from '../access_config.json'; +import accessConfig from './access_config.json'; import { appVersion } from './app-version'; import { ActionModule } from './endpoints/action/action.module'; import { AuthModule } from './endpoints/auth/auth.module'; import { CategoryModule } from './endpoints/category/category.module'; import { FileModule } from './endpoints/file/file.module'; +import { HealthModule } from './endpoints/health/health.module'; +import { FoxgloveModule } from './endpoints/integrations/foxglove.module'; import { MissionModule } from './endpoints/mission/mission.module'; import { ProjectModule } from './endpoints/project/project.module'; import { QueueModule } from './endpoints/queue/queue.module'; import { TagModule } from './endpoints/tag/tag.module'; +import { TemplatesModule } from './endpoints/templates/templates.module'; import { TopicModule } from './endpoints/topic/topic.module'; import { UserModule } from './endpoints/user/user.module'; import { WorkerModule } from './endpoints/worker/worker.module'; @@ -25,7 +29,6 @@ import { APIKeyResolverMiddleware } from './routing/middlewares/api-key-resolver import { AuditLoggerMiddleware } from './routing/middlewares/audit-logger-middleware.service'; import { VersionCheckerMiddlewareService } from './routing/middlewares/version-checker-middleware.service'; import { DBDumper } from './services/dbdumper.service'; -import { AccessGroupConfig } from './types/access-group-config'; @Module({ imports: [ @@ -36,6 +39,7 @@ import { AccessGroupConfig } from './types/access-group-config'; }, }), ConfigModule.forRoot({ + envFilePath: ['.env', '../.env'], isGlobal: true, load: [ configuration, @@ -57,13 +61,15 @@ import { AccessGroupConfig } from './types/access-group-config'; configService.getOrThrow('database.password'), database: configService.getOrThrow('database.database'), - entities: [configService.getOrThrow('entities')], + entities: configService.getOrThrow('entities'), synchronize: env.DEV, logging: ['warn', 'error'], }) as PostgresConnectionOptions, inject: [ConfigService], }), FileModule, + HealthModule, + FoxgloveModule, ProjectModule, TopicModule, MissionModule, @@ -72,6 +78,7 @@ import { AccessGroupConfig } from './types/access-group-config'; AuthModule, PassportModule, ActionModule, + TemplatesModule, TagModule, WorkerModule, CategoryModule, @@ -91,12 +98,16 @@ export class AppModule implements NestModule { * @param consumer */ configure(consumer: MiddlewareConsumer): void { - // Apply APIKeyResolverMiddleware and AuditLoggerMiddleware to all routes consumer - .apply( - APIKeyResolverMiddleware, - AuditLoggerMiddleware, - VersionCheckerMiddlewareService, + .apply(APIKeyResolverMiddleware, AuditLoggerMiddleware) + .forRoutes('*'); + + consumer + .apply(VersionCheckerMiddlewareService) + .exclude( + '/auth/(.*)', // excludes auth endpoints + '/integrations/(.*)', // excludes integration endpoints + '/api/health', // excludes health check ) .forRoutes('*'); } diff --git a/backend/src/decarators.ts b/backend/src/decorators.ts similarity index 100% rename from backend/src/decarators.ts rename to backend/src/decorators.ts diff --git a/backend/src/endpoints/action/action.controller.ts b/backend/src/endpoints/action/action.controller.ts index f22aaacfa..b08425803 100644 --- a/backend/src/endpoints/action/action.controller.ts +++ b/backend/src/endpoints/action/action.controller.ts @@ -1,197 +1,106 @@ +import { ApiOkResponse, OutputDto } from '@/decorators'; +import { ActionService } from '@/services/action.service'; +import { FileService } from '@/services/file.service'; +import { ParameterUuid } from '@/validation/parameter-decorators'; import { - ActionTemplateDto, - ActionTemplatesDto, -} from '@common/api/types/actions/action-template.dto'; -import { ActionDto, ActionsDto } from '@common/api/types/actions/action.dto'; -import { - CreateTemplateDto, - UpdateTemplateDto, -} from '@common/api/types/create-template.dto'; -import { DeleteFileResponseDto } from '@common/api/types/file/delete-file-response.dto'; -import { + ActionDto, + ActionLogsDto, + ActionQuery, + ActionsDto, ActionSubmitResponseDto, + FileEventsDto, + PaginatedQueryDto, SubmitActionDto, -} from '@common/api/types/submit-action-response.dto'; -import { - ActionQuery, SubmitActionMulti, -} from '@common/api/types/submit-action.dto'; -import { IsSkip } from '@common/validation/skip-validation'; -import { IsTake } from '@common/validation/take-validation'; +} from '@kleinkram/api-dto'; import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common'; -import { ApiBody, ApiOperation } from '@nestjs/swagger'; -import { ApiOkResponse, OutputDto } from '../../decarators'; -import { ActionService } from '../../services/action.service'; -import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; -import { - QueryOptionalString, - QuerySkip, - QueryUUID, -} from '../../validation/query-decorators'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { AddUser, AuthHeader } from '../auth/parameter-decorator'; import { - CanCreate, CanCreateAction, CanCreateActions, CanDeleteAction, - CanReadAction, LoggedIn, - UserOnly, } from '../auth/roles.decorator'; -export class RunningActionsQuery { - @IsSkip() - skip?: number; - - @IsTake() - take?: number; -} - -@Controller('action') -export class ActionController { - constructor(private readonly actionService: ActionService) {} +@ApiTags('Actions') +@Controller('actions') +export class ActionsController { + constructor( + private readonly actionService: ActionService, + private readonly fileService: FileService, + ) {} - @Post('submit') + @Post() @CanCreateAction() - @ApiOperation({ - summary: 'Submit an action', - description: - 'Submit an action to the system. This the action is ' + - 'scheduled to run immediately, as soon as resources are available.', - }) - @ApiBody({ - type: SubmitActionDto, - }) - @ApiOkResponse({ - type: ActionSubmitResponseDto, - }) - async createActionRun( + @ApiOperation({ summary: 'Submit (dispatch) a new action' }) + @ApiOkResponse({ type: ActionSubmitResponseDto }) + async create( @Body() dto: SubmitActionDto, @AddUser() user: AuthHeader, ): Promise { return this.actionService.submit(dto, user); } - @Delete(':uuid') - @CanDeleteAction() - @ApiOkResponse({ - description: 'True if the action was deleted', - type: DeleteFileResponseDto, - }) - async deleteAction( - @ParameterUID('uuid') uuid: string, - ): Promise { - await this.actionService.delete(uuid); - return { success: true }; - } - - @Post('multiSubmit') + @Post('batch') @CanCreateActions() - @OutputDto(null) // TODO: type API response - async multiSubmit( + @ApiOperation({ summary: 'Batch submit multiple actions' }) + @ApiOkResponse({ type: [ActionSubmitResponseDto] }) + async createBatch( @Body() dto: SubmitActionMulti, @AddUser() user: AuthHeader, - ) { + ): Promise { return this.actionService.multiSubmit(dto, user); } - @Post('createTemplate') - @CanCreate() - @ApiOkResponse({ - description: 'The created template', - type: ActionTemplateDto, - }) - async createTemplate( - @Body() dto: CreateTemplateDto, - @AddUser() user: AuthHeader, - ): Promise { - return this.actionService.createTemplate(dto, user); - } - - @Post('createNewVersion') - @CanCreate() - @ApiOkResponse({ - description: 'The created template', - type: ActionTemplateDto, - }) - async createNewVersion( - @Body() dto: UpdateTemplateDto, - @AddUser() user: AuthHeader, - ): Promise { - return this.actionService.createNewVersion(dto, user); - } - - @Get('listActions') + @Get() @LoggedIn() - @ApiOperation({ - summary: 'List all actions', - }) - @ApiOkResponse({ - description: 'List of actions', - type: ActionsDto, - }) - async list( - @Query() dto: ActionQuery, + @ApiOperation({ summary: 'List actions (history or running)' }) + @ApiOkResponse({ type: ActionsDto }) + async findAll( + @Query() query: ActionQuery, @AddUser() auth: AuthHeader, - // TODO: bring back filter options ): Promise { - let missionUuid = dto.missionUuid; - if (auth.apikey) { - missionUuid = auth.apikey.mission.uuid; - } - return this.actionService.listActions( - dto.projectUuid, - missionUuid, - auth.user.uuid, - Number.parseInt((dto.skip ?? 0).toString()), - Number.parseInt((dto.take ?? 0).toString()), - dto.sortBy ?? '', - (dto.sortDirection as 'ASC' | 'DESC') ?? 'DESC', - dto.search ?? '', - ); + return this.actionService.findAll(query, auth); } - @Get('running') - @UserOnly() - @ApiOkResponse({ - description: 'List of running actions', - type: ActionsDto, - }) - async runningActions( - @AddUser() auth: AuthHeader, - @Query() parameters: RunningActionsQuery, - ): Promise { - return this.actionService.runningActions( - auth.user.uuid, - parameters.skip ?? 0, - parameters.take ?? 10, - ); + @Get(':uuid') + @LoggedIn() + @ApiOperation({ summary: 'Get action details' }) + @ApiOkResponse({ type: ActionDto }) + async findOne(@ParameterUuid('uuid') uuid: string): Promise { + return this.actionService.details(uuid); } - @Get('listTemplates') + @Get(':uuid/logs') @LoggedIn() - @ApiOkResponse({ - description: 'List of templates', - type: ActionTemplatesDto, - }) - async listTemplates( - @AddUser() user: AuthHeader, - @QuerySkip('skip') skip: number, - @QuerySkip('take') take: number, - @QueryOptionalString('search', 'Searchkey in name') search: string, - ): Promise { - return this.actionService.listTemplates(skip, take, search); + @ApiOperation({ summary: 'Get action logs' }) + @ApiOkResponse({ type: ActionLogsDto }) + async getLogs( + @ParameterUuid('uuid') uuid: string, + @Query() query: PaginatedQueryDto, + ): Promise { + return this.actionService.getLogs(uuid, query); } - @Get('details') - @CanReadAction() - @ApiOkResponse({ - description: 'Details of the action', - type: ActionDto, - }) - async details( - @QueryUUID('uuid', 'ActionUUID') uuid: string, - ): Promise { - return this.actionService.details(uuid); + @Get(':uuid/file-events') + @LoggedIn() + @ApiOperation({ summary: 'Get file events triggered by this action' }) + @ApiOkResponse({ type: FileEventsDto }) + async getFileEvents( + @ParameterUuid('uuid') uuid: string, + ): Promise { + return this.fileService.getActionFileEvents(uuid); + } + + @Delete(':uuid') + @CanDeleteAction() + @ApiOperation({ summary: 'Delete a specific action run' }) + @OutputDto(null) + async remove( + @ParameterUuid('uuid') uuid: string, + ): Promise<{ success: boolean }> { + await this.actionService.delete(uuid); + return { success: true }; } } diff --git a/backend/src/endpoints/action/action.module.ts b/backend/src/endpoints/action/action.module.ts index 39d4d7c8e..06f8e12d9 100644 --- a/backend/src/endpoints/action/action.module.ts +++ b/backend/src/endpoints/action/action.module.ts @@ -1,18 +1,19 @@ -import ActionTemplateEntity from '@common/entities/action/action-template.entity'; -import ActionEntity from '@common/entities/action/action.entity'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import { ActionDispatcherModule } from '@common/modules/action-dispatcher/action-dispatcher.module'; -import { StorageModule } from '@common/modules/storage/storage.module'; +import { ActionService } from '@/services/action.service'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { ActionTemplateEntity } from '@kleinkram/backend-common/entities/action/action-template.entity'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; +import { AccountEntity } from '@kleinkram/backend-common/entities/auth/account.entity'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { ActionDispatcherModule } from '@kleinkram/backend-common/modules/action-dispatcher/action-dispatcher.module'; +import { StorageModule } from '@kleinkram/backend-common/modules/storage/storage.module'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ActionService } from '../../services/action.service'; import { ActionGuardService } from '../auth/action-guard.service'; +import { FileModule } from '../file/file.module'; import { QueueModule } from '../queue/queue.module'; -import { ActionController } from './action.controller'; +import { ActionsController } from './action.controller'; @Module({ imports: [ @@ -27,10 +28,13 @@ import { ActionController } from './action.controller'; ]), QueueModule, StorageModule, + StorageModule, ActionDispatcherModule, + FileModule, ], providers: [ActionService, ActionGuardService], exports: [ActionService], - controllers: [ActionController], + controllers: [ActionsController], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class ActionModule {} diff --git a/backend/src/endpoints/auth/access.controller.ts b/backend/src/endpoints/auth/access.controller.ts index 170e008be..145bfe008 100644 --- a/backend/src/endpoints/auth/access.controller.ts +++ b/backend/src/endpoints/auth/access.controller.ts @@ -1,15 +1,21 @@ -import { AccessGroupsDto } from '@common/api/types/access-control/access-groups.dto'; -import { GetFilteredAccessGroupsDto } from '@common/api/types/access-control/get-filtered-access-groups.dto'; -import { AddAccessGroupToProjectDto } from '@common/api/types/add-access-group-project.dto'; -import { AddUserToAccessGroupDto } from '@common/api/types/add-user-access-group.dto'; -import { AddUserToProjectDto } from '@common/api/types/add-user-project.dto'; -import { CreateAccessGroupDto } from '@common/api/types/create-access-group.dto'; -import { ProjectWithMissionsDto } from '@common/api/types/project/project-with-missions.dto'; -import { RemoveAccessGroupFromProjectDto } from '@common/api/types/remove-access-group-project.dto'; -import { SetAccessGroupUserExpirationDto } from '@common/api/types/set-access-group-user-expiration.dto'; -import { AccessGroupDto, GroupMembershipDto } from '@common/api/types/user.dto'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; +import { ApiOkResponse, ApiResponse, OutputDto } from '@/decorators'; +import { AccessService } from '@/services/access.service'; +import { QueryUUID } from '@/validation/query-decorators'; +import { + AccessGroupDto, + AccessGroupsDto, + AddAccessGroupToProjectDto, + AddUserToAccessGroupDto, + AddUserToProjectDto, + CreateAccessGroupDto, + GetFilteredAccessGroupsDto, + GroupMembershipDto, + ProjectWithMissionsDto, + RemoveAccessGroupFromProjectDto, + SetAccessGroupUserExpirationDto, +} from '@kleinkram/api-dto'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; import { Body, ConflictException, @@ -22,10 +28,7 @@ import { } from '@nestjs/common'; import { ApiOperation } from '@nestjs/swagger'; import { EntityNotFoundError } from 'typeorm'; -import { ApiOkResponse, ApiResponse, OutputDto } from '../../decarators'; -import { AccessService } from '../../services/access.service'; import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; -import { QueryUUID } from '../../validation/query-decorators'; import { AddUser, AuthHeader } from './parameter-decorator'; import { CanCreate, diff --git a/backend/src/endpoints/auth/action-guard.service.ts b/backend/src/endpoints/auth/action-guard.service.ts index cf44cacf0..55f1882a0 100644 --- a/backend/src/endpoints/auth/action-guard.service.ts +++ b/backend/src/endpoints/auth/action-guard.service.ts @@ -1,12 +1,12 @@ -import ActionEntity from '@common/entities/action/action.entity'; -import ApikeyEntity from '@common/entities/auth/apikey.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { AccessGroupRights, UserRole } from '@common/frontend_shared/enum'; +import { ProjectGuardService } from '@/services/project-guard.service'; +import { ApiKeyEntity } from '@kleinkram/backend-common'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { AccessGroupRights, UserRole } from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import logger from '../../logger'; -import { ProjectGuardService } from '../../services/project-guard.service'; import { MissionGuardService } from './mission-guard.service'; @Injectable() @@ -23,6 +23,7 @@ export class ActionGuardService { actionUuid: string, rights: AccessGroupRights = AccessGroupRights.READ, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!actionUuid || !user) { logger.error( `ActionGuard: actionUUID (${actionUuid}) or User (${user.uuid}) not provided. Requesting ${rights.toString()} access.`, @@ -61,7 +62,7 @@ export class ActionGuardService { } async canKeyAccessAction( - apikey: ApikeyEntity, + apikey: ApiKeyEntity, actionUuid: string, rights: AccessGroupRights = AccessGroupRights.READ, ): Promise { diff --git a/backend/src/endpoints/auth/auth-guard.service.ts b/backend/src/endpoints/auth/auth-guard.service.ts index 691bde9d4..be240987d 100644 --- a/backend/src/endpoints/auth/auth-guard.service.ts +++ b/backend/src/endpoints/auth/auth-guard.service.ts @@ -1,7 +1,7 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import GroupMembershipEntity from '@common/entities/auth/group-membership.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { UserRole } from '@common/frontend_shared/enum'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { GroupMembershipEntity } from '@kleinkram/backend-common/entities/auth/group-membership.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { UserRole } from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -20,6 +20,7 @@ export class AuthGuardService { user: UserEntity, projectAccessUUID: string, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!user || !projectAccessUUID) { logger.error( `AuthGuard: projectAccessUUID (${projectAccessUUID}) or User (${user.uuid}) not provided.`, @@ -31,6 +32,7 @@ export class AuthGuardService { } return await this.accessGroupRepository.exists({ where: { + // eslint-disable-next-line @typescript-eslint/naming-convention project_accesses: { uuid: projectAccessUUID }, creator: { uuid: user.uuid }, }, @@ -41,6 +43,7 @@ export class AuthGuardService { user: UserEntity, uuid: string, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!user || !uuid) { logger.error( `AuthGuard: aguUUID (${uuid}) or User (${user.uuid}) not provided.`, diff --git a/backend/src/endpoints/auth/auth-helper.ts b/backend/src/endpoints/auth/auth-helper.ts index 0d1b23566..e08e9bb2a 100644 --- a/backend/src/endpoints/auth/auth-helper.ts +++ b/backend/src/endpoints/auth/auth-helper.ts @@ -1,14 +1,15 @@ -import File from '@common/entities/file/file.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { UserRole } from '@common/frontend_shared/enum'; -import { MissionAccessViewEntity } from '@common/viewEntities/mission-access-view.entity'; -import { ProjectAccessViewEntity } from '@common/viewEntities/project-access-view.entity'; +import { FileEntity as File } from '@kleinkram/backend-common/entities/file/file.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { MissionAccessViewEntity } from '@kleinkram/backend-common/viewEntities/mission-access-view.entity'; +import { ProjectAccessViewEntity } from '@kleinkram/backend-common/viewEntities/project-access-view.entity'; +import { UserRole } from '@kleinkram/shared'; import { Brackets, SelectQueryBuilder } from 'typeorm'; import { v4 as uuidv4 } from 'uuid'; export const projectAccessUUIDQuery = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any query: SelectQueryBuilder, userUUID: string, tok: string | undefined = undefined, @@ -16,6 +17,7 @@ export const projectAccessUUIDQuery = ( // we us randomized tokens to creat a signature for the subquery parameters // in this way we avoid conflicts with other subqueries or the main query // would be nice if typeorm did this out of the box, but it doesnt + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (tok === undefined) tok = uuidv4().replaceAll('-', ''); const projectIdsQuery = query @@ -30,6 +32,7 @@ export const projectAccessUUIDQuery = ( }; export const missionAccessUUIDQuery = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any query: SelectQueryBuilder, userUUID: string, tok: string | undefined = undefined, @@ -37,6 +40,7 @@ export const missionAccessUUIDQuery = ( // we us randomized tokens to creat a signature for the subquery parameters // in this way we avoid conflicts with other subqueries or the main query // would be nice if typeorm did this out of the box, but it doesnt + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (tok === undefined) tok = uuidv4().replaceAll('-', ''); return query @@ -49,13 +53,16 @@ export const missionAccessUUIDQuery = ( }; export const getUserIsAdminSubQuery = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any query: SelectQueryBuilder, userUUID: string, tok: string | undefined = undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): SelectQueryBuilder => { // we us randomized tokens to creat a signature for the subquery parameters // in this way we avoid conflicts with other subqueries or the main query // would be nice if typeorm did this out of the box, but it doesnt + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (tok === undefined) tok = uuidv4().replaceAll('-', ''); const subQuery = query @@ -149,8 +156,10 @@ export const addAccessConstraintsToFileQuery = ( // TODO: deprecate this in favor of the above functions export function addAccessConstraints( + // eslint-disable-next-line @typescript-eslint/no-explicit-any qb: SelectQueryBuilder, userUUID: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): SelectQueryBuilder { // Add project access join qb.leftJoin( diff --git a/backend/src/endpoints/auth/auth.controller.ts b/backend/src/endpoints/auth/auth.controller.ts index 9d89e7cc3..7d20ff045 100644 --- a/backend/src/endpoints/auth/auth.controller.ts +++ b/backend/src/endpoints/auth/auth.controller.ts @@ -1,6 +1,10 @@ -import UserEntity from '@common/entities/user/user.entity'; -import env from '@common/environment'; -import { CookieNames, Providers } from '@common/frontend_shared/enum'; +import { OutputDto } from '@/decorators'; +import { AuthService } from '@/services/auth.service'; +import { UserService } from '@/services/user.service'; +import { AvailableProvidersDto } from '@kleinkram/api-dto'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import env from '@kleinkram/backend-common/environment'; +import { CookieNames, Providers } from '@kleinkram/shared'; import { Controller, Get, @@ -13,9 +17,6 @@ import { import { JwtService } from '@nestjs/jwt'; import { AuthGuard } from '@nestjs/passport'; import { Request, Response } from 'express'; -import { OutputDto } from '../../decarators'; -import { AuthService } from '../../services/auth.service'; -import { UserService } from '../../services/user.service'; import { InvalidJwtTokenException } from './jwt.strategy'; import { UserOnly } from './roles.decorator'; @@ -27,9 +28,20 @@ export class AuthController { private userService: UserService, ) {} + @Get('available-providers') + @OutputDto(null) + getAvailableProviders(): AvailableProvidersDto { + return { + google: !!env.GOOGLE_CLIENT_ID && !!env.GOOGLE_CLIENT_SECRET, + github: !!env.GITHUB_CLIENT_ID && !!env.GITHUB_CLIENT_SECRET, + fakeOauth: env.VITE_USE_FAKE_OAUTH_FOR_DEVELOPMENT, + }; + } + @Get('github') @UseGuards(AuthGuard('github')) - async githubAuth(): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars + async githubAuth(@Req() request: any): Promise { // Initiates the GitHub OAuth flow // OAuth is handled by the AuthGuard } @@ -51,21 +63,33 @@ export class AuthController { @Get('github/callback') @UseGuards(AuthGuard('github')) @OutputDto(null) // TODO: type API response - githubAuthRedirect(@Req() request, @Res() response: Response): void { + // eslint-disable-next-line @typescript-eslint/require-await + async githubAuthCallback( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + @Req() request: any, + @Res() response: Response, + ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument this.handleAuthRedirect(request, response); } @Get('google/callback') @UseGuards(AuthGuard('google')) @OutputDto(null) // TODO: type API response - googleAuthRedirect(@Req() request, @Res() response: Response): void { + googleAuthRedirect( + @Req() request: Request, + @Res() response: Response, + ): void { this.handleAuthRedirect(request, response); } @Get('fake-oauth/callback') @UseGuards(AuthGuard(Providers.FakeOAuth)) @OutputDto(null) // TODO: type API response - fakeOAuthAuthRedirect(@Req() request, @Res() response: Response): void { + fakeOAuthAuthRedirect( + @Req() request: Request, + @Res() response: Response, + ): void { if (!env.VITE_USE_FAKE_OAUTH_FOR_DEVELOPMENT) throw new MethodNotAllowedException(); this.handleAuthRedirect(request, response); @@ -77,7 +101,7 @@ export class AuthController { ): void { const user = request.user; const token = this.authService.login(user as UserEntity); - const state = request.query['state']; + const state = request.query.state; if (state === 'cli') { response.redirect( @@ -105,11 +129,11 @@ export class AuthController {

Authentication Successful

Please copy your tokens from below and paste them back into your application.

- +

Authentication Token

${authToken}
- +

Refresh Token

${refreshToken}
@@ -160,6 +184,7 @@ export class AuthController { @Post('refresh-token') @OutputDto(null) // TODO: type API response async refreshToken(@Req() request: Request, @Res() response: Response) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const refreshToken = request.cookies[CookieNames.REFRESH_TOKEN]; if (!refreshToken) { return response @@ -168,15 +193,19 @@ export class AuthController { } try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument const payload = this.jwtService.verify(refreshToken); // @ts-ignore + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access const user = await this.userService.findOneByUUID(payload.uuid, { uuid: true, email: true, role: true, }); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!user) { return response .status(401) @@ -192,7 +221,7 @@ export class AuthController { secure: env.DEV, sameSite: 'strict', }); - response.status(200).json({ message: 'Token refreshed' }); + return response.status(200).json({ message: 'Token refreshed' }); } catch { throw InvalidJwtTokenException; } diff --git a/backend/src/endpoints/auth/auth.module.ts b/backend/src/endpoints/auth/auth.module.ts index 70aec8a34..c678bbb91 100644 --- a/backend/src/endpoints/auth/auth.module.ts +++ b/backend/src/endpoints/auth/auth.module.ts @@ -1,27 +1,30 @@ +import { AuthService } from '@/services/auth.service'; import { Global, Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AuthService } from '../../services/auth.service'; import { AuthController } from './auth.controller'; import { GoogleStrategy } from './google.strategy'; import { JwtStrategy } from './jwt.strategy'; import { AdminOnlyGuard, LoggedInUserGuard } from './roles.guard'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import GroupMembershipEntity from '@common/entities/auth/group-membership.entity'; -import MissionAccessEntity from '@common/entities/auth/mission-access.entity'; -import ProjectAccessEntity from '@common/entities/auth/project-access.entity'; -import FileEntity from '@common/entities/file/file.entity'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import env from '@common/environment'; -import { MissionAccessViewEntity } from '@common/viewEntities/mission-access-view.entity'; -import { ProjectAccessViewEntity } from '@common/viewEntities/project-access-view.entity'; -import { AccessService } from '../../services/access.service'; -import { ProjectGuardService } from '../../services/project-guard.service'; +import { AccessService } from '@/services/access.service'; +import { ProjectGuardService } from '@/services/project-guard.service'; +import { + AccessGroupEntity, + AccountEntity, + AffiliationGroupService, + GroupMembershipEntity, + MissionAccessEntity, + ProjectAccessEntity, +} from '@kleinkram/backend-common'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import env from '@kleinkram/backend-common/environment'; +import { MissionAccessViewEntity } from '@kleinkram/backend-common/viewEntities/mission-access-view.entity'; +import { ProjectAccessViewEntity } from '@kleinkram/backend-common/viewEntities/project-access-view.entity'; import { AccessController } from './access.controller'; import { AuthGuardService } from './auth-guard.service'; import { FakeOauthStrategy } from './fake-oauth.strategy'; @@ -54,6 +57,7 @@ import { MissionGuardService } from './mission-guard.service'; ], providers: [ AuthService, + AffiliationGroupService, AccessService, GoogleStrategy, GitHubStrategy, @@ -77,4 +81,5 @@ import { MissionGuardService } from './mission-guard.service'; ]), ], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class AuthModule {} diff --git a/backend/src/endpoints/auth/fake-oauth.strategy.ts b/backend/src/endpoints/auth/fake-oauth.strategy.ts index 8969433e0..e77d1b0fa 100644 --- a/backend/src/endpoints/auth/fake-oauth.strategy.ts +++ b/backend/src/endpoints/auth/fake-oauth.strategy.ts @@ -1,5 +1,6 @@ -import env from '@common/environment'; -import { Providers } from '@common/frontend_shared/enum'; +import { AuthService } from '@/services/auth.service'; +import env from '@kleinkram/backend-common/environment'; +import { Providers } from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import e from 'express'; @@ -9,7 +10,6 @@ import { VerifyCallback, } from 'passport-oauth2'; import logger from '../../logger'; -import { AuthService } from '../../services/auth.service'; /** * @@ -30,21 +30,30 @@ export class FakeOauthStrategy extends PassportStrategy( // this is used for local development and testing purposes only clientID: 'some-random-string-it-does-not-matter', clientSecret: 'some-random-string-it-does-not-matter', - callbackURL: `${env.ENDPOINT}/auth/fake-oauth/callback`, + callbackURL: `${env.BACKEND_URL}/auth/fake-oauth/callback`, scope: [], } as StrategyOptions); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any authenticate(request: e.Request, options?: any) { - options.state = request.query['state']; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + options.state = request.query.state; + // Pass the user parameter to the OAuth provider for auto-login + if (request.query.user) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + options.user = request.query.user; + } super.authenticate(request, options); } async validate( accessToken: string, refreshToken: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any profile: any, callback: VerifyCallback, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { // fetch profile from http://fake-oauth:5000/oauth/profile @@ -57,12 +66,14 @@ export class FakeOauthStrategy extends PassportStrategy( }, ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const fetchedProfile = await fetchedProfileResponse.json(); const user = await this.authService.validateAndCreateUserByFakeOAuth( fetchedProfile, ); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (user) { logger.debug(`Login successful for ${user.uuid}`); callback(null, user); diff --git a/backend/src/endpoints/auth/github.strategy.ts b/backend/src/endpoints/auth/github.strategy.ts index f9819e4d3..f8ae81a93 100644 --- a/backend/src/endpoints/auth/github.strategy.ts +++ b/backend/src/endpoints/auth/github.strategy.ts @@ -1,40 +1,48 @@ -import env from '@common/environment'; -import { Providers } from '@common/frontend_shared/enum'; +import { AuthService } from '@/services/auth.service'; +import { AuthFlowException } from '@/types/auth-flow-exception'; +import env from '@kleinkram/backend-common/environment'; +import { Providers } from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import e from 'express'; -import { Strategy, VerifyCallback } from 'passport-github2'; +import { Strategy } from 'passport-github2'; import logger from '../../logger'; -import { AuthService } from '../../services/auth.service'; -import { AuthFlowException } from '../../types/auth-flow-exception'; @Injectable() export class GitHubStrategy extends PassportStrategy(Strategy, 'github') { constructor(private authService: AuthService) { super({ - clientID: env.GITHUB_CLIENT_ID, - clientSecret: env.GITHUB_CLIENT_SECRET, - callbackURL: `${env.ENDPOINT}/auth/github/callback`, + clientID: env.GITHUB_CLIENT_ID ?? 'dummy', + clientSecret: env.GITHUB_CLIENT_SECRET ?? 'dummy', + callbackURL: `${env.BACKEND_URL}/auth/github/callback`, scope: ['user:email', 'user:profile'], }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any authenticate(request: e.Request, options?: any) { - options.state = request.query['state']; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + options.state = request.query.state; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument super.authenticate(request, options); } async validate( accessToken: string, refreshToken: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any profile: any, - callback: VerifyCallback, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { provider } = profile; // currently only github is supported if (provider !== Providers.GITHUB) { logger.error('Invalid provider, expected github but got', provider); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call callback(new AuthFlowException('Invalid provider!')); return; } @@ -42,12 +50,15 @@ export class GitHubStrategy extends PassportStrategy(Strategy, 'github') { const user = await this.authService.validateAndCreateUserByGitHub(profile); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (user) { logger.debug(`Login successful for ${user.uuid}`); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call callback(null, user); return; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-call callback(null); return; } diff --git a/backend/src/endpoints/auth/google.strategy.ts b/backend/src/endpoints/auth/google.strategy.ts index 32a11e95e..c5e3e5c27 100644 --- a/backend/src/endpoints/auth/google.strategy.ts +++ b/backend/src/endpoints/auth/google.strategy.ts @@ -1,35 +1,40 @@ -import env from '@common/environment'; -import { Providers } from '@common/frontend_shared/enum'; +import { AuthService } from '@/services/auth.service'; +import { AuthFlowException } from '@/types/auth-flow-exception'; +import env from '@kleinkram/backend-common/environment'; +import { Providers } from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import e from 'express'; import { Strategy, VerifyCallback } from 'passport-google-oauth20'; import logger from '../../logger'; -import { AuthService } from '../../services/auth.service'; -import { AuthFlowException } from '../../types/auth-flow-exception'; @Injectable() export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { constructor(private authService: AuthService) { super({ - clientID: env.GOOGLE_CLIENT_ID, - clientSecret: env.GOOGLE_CLIENT_SECRET, - callbackURL: `${env.ENDPOINT}/auth/google/callback`, + clientID: env.GOOGLE_CLIENT_ID ?? 'dummy', + clientSecret: env.GOOGLE_CLIENT_SECRET ?? 'dummy', + callbackURL: `${env.BACKEND_URL}/auth/google/callback`, scope: ['email', 'profile'], }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any authenticate(request: e.Request, options?: any) { - options.state = request.query['state']; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + options.state = request.query.state; super.authenticate(request, options); } async validate( accessToken: string, refreshToken: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any profile: any, callback: VerifyCallback, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { provider } = profile; // currently only google is supported @@ -42,6 +47,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { const user = await this.authService.validateAndCreateUserByGoogle(profile); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (user) { logger.debug(`Login successful for ${user.uuid}`); callback(null, user); diff --git a/backend/src/endpoints/auth/jwt.strategy.ts b/backend/src/endpoints/auth/jwt.strategy.ts index 01c8fe89b..e41eabc1d 100644 --- a/backend/src/endpoints/auth/jwt.strategy.ts +++ b/backend/src/endpoints/auth/jwt.strategy.ts @@ -1,5 +1,5 @@ -import env from '@common/environment'; -import { CookieNames } from '@common/frontend_shared/enum'; +import env from '@kleinkram/backend-common/environment'; +import { CookieNames } from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Request } from 'express'; @@ -18,22 +18,27 @@ export class JwtStrategy extends PassportStrategy(Strategy) { super({ jwtFromRequest: (request: Request) => { let token = null; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (request.cookies) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment token = request.cookies[CookieNames.AUTH_TOKEN]; } - return token; + return token as string | null; }, ignoreExpiration: false, secretOrKey: env.JWT_SECRET, }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any async validate(payload: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (payload.uuid) { try { const user = await this.userService.findOneByUUID( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access payload.uuid, - {}, + { uuid: true, role: true, name: true }, {}, ); return { user }; @@ -42,6 +47,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } } return { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access uuid: payload.uuid, }; } diff --git a/backend/src/endpoints/auth/mission-guard.service.ts b/backend/src/endpoints/auth/mission-guard.service.ts index 5315e371e..1a31e4792 100644 --- a/backend/src/endpoints/auth/mission-guard.service.ts +++ b/backend/src/endpoints/auth/mission-guard.service.ts @@ -1,15 +1,15 @@ -import ApikeyEntity from '@common/entities/auth/apikey.entity'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { AccessGroupRights, UserRole } from '@common/frontend_shared/enum'; -import { MissionAccessViewEntity } from '@common/viewEntities/mission-access-view.entity'; +import { ProjectGuardService } from '@/services/project-guard.service'; +import { ApiKeyEntity } from '@kleinkram/backend-common'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { MissionAccessViewEntity } from '@kleinkram/backend-common/viewEntities/mission-access-view.entity'; +import { AccessGroupRights, UserRole } from '@kleinkram/shared'; import { ConflictException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { isUUID } from 'class-validator'; import { MoreThanOrEqual, Repository } from 'typeorm'; import logger from '../../logger'; -import { ProjectGuardService } from '../../services/project-guard.service'; @Injectable() export class MissionGuardService { @@ -35,6 +35,7 @@ export class MissionGuardService { return false; } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!user) { return false; } @@ -50,6 +51,7 @@ export class MissionGuardService { projectUuid: string, rights: AccessGroupRights = AccessGroupRights.READ, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!missionName || !user) { logger.error( `MissionGuard: missionName (${missionName}) or User (${user.uuid}) not provided. Requesting ${rights.toString()} access.`, @@ -69,6 +71,7 @@ export class MissionGuardService { tagUUID: string, rights: AccessGroupRights = AccessGroupRights.READ, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!tagUUID || !user) { logger.error( `MissionGuard: tagUUID (${tagUUID}) or User (${user.uuid}) not provided. Requesting ${rights.toString()} access.`, @@ -95,7 +98,7 @@ export class MissionGuardService { } async canKeyTagMission( - apikey: ApikeyEntity, + apikey: ApiKeyEntity, tagUUID: string, rights: AccessGroupRights = AccessGroupRights.READ, ): Promise { @@ -116,12 +119,14 @@ export class MissionGuardService { missionUUIDs: string[], rights: AccessGroupRights = AccessGroupRights.READ, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!missionUUIDs || !user) { logger.error( `MissionGuard: missionUUIDs (${missionUUIDs.toString()}) or User (${user.uuid}) not provided. Requesting READ access.`, ); return false; } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!user) { return false; } @@ -171,7 +176,7 @@ export class MissionGuardService { } canKeyAccessMission( - apikey: ApikeyEntity, + apikey: ApiKeyEntity, missionUUID: string, rights: AccessGroupRights = AccessGroupRights.READ, ): boolean { @@ -179,7 +184,7 @@ export class MissionGuardService { } async canKeyAccessMissionByName( - apikey: ApikeyEntity, + apikey: ApiKeyEntity, missionName: string, projectUUID: string, rights: AccessGroupRights = AccessGroupRights.READ, diff --git a/backend/src/endpoints/auth/parameter-decorator.ts b/backend/src/endpoints/auth/parameter-decorator.ts index b82714c48..8b6804eca 100644 --- a/backend/src/endpoints/auth/parameter-decorator.ts +++ b/backend/src/endpoints/auth/parameter-decorator.ts @@ -1,5 +1,5 @@ -import ApikeyEntity from '@common/entities/auth/apikey.entity'; -import UserEntity from '@common/entities/user/user.entity'; +import { ApiKeyEntity } from '@kleinkram/backend-common'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; import { createParamDecorator, ExecutionContext } from '@nestjs/common'; /** @@ -9,16 +9,19 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'; * */ export const AddUser = createParamDecorator((_, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (request.user === undefined || request.user === null) { throw new Error('User not authenticated'); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return request.user; }); export interface AuthHeader { user: UserEntity; - apikey?: ApikeyEntity; + apiKey?: ApiKeyEntity; } diff --git a/backend/src/endpoints/auth/roles.decorator.ts b/backend/src/endpoints/auth/roles.decorator.ts index ce75f83fc..43e47075d 100644 --- a/backend/src/endpoints/auth/roles.decorator.ts +++ b/backend/src/endpoints/auth/roles.decorator.ts @@ -1,11 +1,11 @@ -import { UnauthorizedExceptionDto } from '@common/api/types/exceptions/unauthorized-exception.dto'; +import { UnauthorizedExceptionDto } from '@kleinkram/api-dto'; import { applyDecorators, ForbiddenException, SetMetadata, UseGuards, } from '@nestjs/common'; -import { ApiResponse } from '../../decarators'; +import { ApiResponse } from '../../decorators'; import { AddTagGuard, AdminOnlyGuard, diff --git a/backend/src/endpoints/auth/roles.guard.ts b/backend/src/endpoints/auth/roles.guard.ts index 0e3795256..4145b722a 100644 --- a/backend/src/endpoints/auth/roles.guard.ts +++ b/backend/src/endpoints/auth/roles.guard.ts @@ -1,9 +1,10 @@ -import ApikeyEntity from '@common/entities/auth/apikey.entity'; +import { ApiKeyEntity } from '@kleinkram/backend-common'; import { AccessGroupRights, ActionState, + KeyTypes, UserRole, -} from '@common/frontend_shared/enum'; +} from '@kleinkram/shared'; import { BadRequestException, CanActivate, @@ -17,14 +18,14 @@ import { AuthGuard } from '@nestjs/passport'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import ActionTemplateEntity from '@common/entities/action/action-template.entity'; -import ActionEntity from '@common/entities/action/action.entity'; -import { FileGuardService } from '../../services/file-guard.service'; -import { ProjectGuardService } from '../../services/project-guard.service'; -import { UserService } from '../../services/user.service'; -import { ActionGuardService } from './action-guard.service'; -import { AuthGuardService } from './auth-guard.service'; -import { MissionGuardService } from './mission-guard.service'; +import { ActionGuardService } from '@/endpoints/auth/action-guard.service'; +import { AuthGuardService } from '@/endpoints/auth/auth-guard.service'; +import { MissionGuardService } from '@/endpoints/auth/mission-guard.service'; +import { FileGuardService } from '@/services/file-guard.service'; +import { ProjectGuardService } from '@/services/project-guard.service'; +import { UserService } from '@/services/user.service'; +import { ActionTemplateEntity } from '@kleinkram/backend-common/entities/action/action-template.entity'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; @Injectable() export class PublicGuard implements CanActivate { @@ -35,14 +36,20 @@ export class PublicGuard implements CanActivate { export class BaseGuard extends AuthGuard('jwt') { async getUser(context: ExecutionContext) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!request.user) { await super.canActivate(context); // Ensure the user is authenticated first by reading JWT } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const { user, apiKey } = request.user; if (!user) { throw new UnauthorizedException('User not logged in'); } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment return { user, apiKey, request }; } } @@ -58,6 +65,7 @@ export class LoggedInUserGuard extends BaseGuard { @Injectable() export class UserGuard extends BaseGuard { async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { apiKey } = await this.getUser(context); // Will throw if not logged in if (apiKey) { throw new UnauthorizedException( @@ -75,6 +83,7 @@ export class AdminOnlyGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey } = await this.getUser(context); if (apiKey) { @@ -82,6 +91,7 @@ export class AdminOnlyGuard extends BaseGuard { } const databaseUser = await this.userService.findOneByUUID( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access user.uuid, { role: true }, {}, @@ -102,12 +112,34 @@ export class ReadProjectGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing + const projectUUID = request.query.uuid || request.params.uuid; + if (apiKey) { + // Check if this is an action API key + if ( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + apiKey.key_type === KeyTypes.ACTION && + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + apiKey.mission?.project?.uuid + ) { + // Action keys can only access their associated project + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (apiKey.mission.project.uuid !== projectUUID) { + throw new ForbiddenException( + 'Action key cannot access this project', + ); + } + return true; + } + // CLI keys and other keys cannot read projects throw new UnauthorizedException('CLI Keys cannot read projects'); } - const projectUUID = request.query.uuid || request.params.uuid; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return this.projectGuardService.canAccessProject(user, projectUUID); } } @@ -122,14 +154,19 @@ export class ReadProjectByNameGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); if (apiKey) { throw new UnauthorizedException('CLI Keys cannot read projects'); } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const projectName = request.query.name; return this.projectGuardService.canAccessProjectByName( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument projectName, ); } @@ -145,14 +182,19 @@ export class CreateInProjectByBodyGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); if (apiKey) { throw new UnauthorizedException('CLI Keys cannot read projects'); } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const projectUUID = request.body.projectUUID; return this.projectGuardService.canAccessProject( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument projectUUID, AccessGroupRights.CREATE, ); @@ -169,15 +211,20 @@ export class WriteProjectGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); if (apiKey) { throw new UnauthorizedException('CLI Keys cannot write projects'); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const projectUUID = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing request.query.uuid || request.body.uuid || request.params.uuid; return this.projectGuardService.canAccessProject( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument projectUUID, AccessGroupRights.WRITE, ); @@ -191,15 +238,21 @@ export class DeleteProjectGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); if (apiKey) { throw new UnauthorizedException('CLI Keys cannot delete projects'); } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const projectUUID = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing request.query.uuid || request.params.uuid || request.body.uuid; return this.projectGuardService.canAccessProject( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument projectUUID, AccessGroupRights.DELETE, ); @@ -213,20 +266,27 @@ export class DeleteFileGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const fileUUID = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing request.query.uuid || request.body.uuid || request.params.uuid; if (apiKey) { return this.fileGuardService.canKeyAccessFile( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument fileUUID, AccessGroupRights.DELETE, ); } return this.fileGuardService.canAccessFile( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument fileUUID, AccessGroupRights.DELETE, ); @@ -243,10 +303,12 @@ export class CreateGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey } = await this.getUser(context); if (apiKey) { throw new UnauthorizedException('CLI Keys cannot create projects'); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return this.projectGuardService.canCreate(user); } } @@ -261,16 +323,22 @@ export class ReadMissionGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUID = request.query.uuid; if (apiKey) { return this.missionGuardService.canKeyAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.READ, ); } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return this.missionGuardService.canAccessMission(user, missionUUID); } } @@ -285,15 +353,20 @@ export class CanReadManyMissionsGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); if (apiKey) { throw new UnauthorizedException( 'CLI Keys cannot read many missions', ); } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUIDs = request.query.uuids; return await this.missionGuardService.canReadManyMissions( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUIDs, ); } @@ -309,22 +382,32 @@ export class ReadMissionByNameGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionName = request.query.name; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const projectUuid = request.query.projectUUID; if (apiKey) { return this.missionGuardService.canKeyAccessMissionByName( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionName, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument projectUuid, AccessGroupRights.READ, ); } return this.missionGuardService.canAccessMissionByName( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionName, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument projectUuid, ); } @@ -340,18 +423,24 @@ export class CreateInMissionByBodyGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUID = request.body.missionUUID; if (apiKey) { return this.missionGuardService.canKeyAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.CREATE, ); } return this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.CREATE, ); @@ -362,23 +451,31 @@ export class CreateInMissionByBodyGuard extends BaseGuard { export class WriteMissionByBodyGuard extends BaseGuard { constructor( private missionGuardService: MissionGuardService, + private reflector: Reflector, ) { super(); } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUID = request.body.missionUUID; if (apiKey) { return this.missionGuardService.canKeyAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.WRITE, ); } return this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.WRITE, ); @@ -395,20 +492,29 @@ export class CanDeleteMissionGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const missionUUID = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing request.body.uuid || + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing request.params.uuid || + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access request.body.missionUUID; if (apiKey) { return this.missionGuardService.canKeyAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.DELETE, ); } return this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.DELETE, ); @@ -419,25 +525,32 @@ export class CanDeleteMissionGuard extends BaseGuard { export class AddTagGuard extends BaseGuard { constructor( private missionGuardService: MissionGuardService, - @InjectRepository(ApikeyEntity) - private apikeyRepository: Repository, + @InjectRepository(ApiKeyEntity) + private apikeyRepository: Repository, private reflector: Reflector, ) { super(); } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUID = request.body.mission; if (apiKey) { return this.missionGuardService.canKeyAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.WRITE, ); } return this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.WRITE, ); @@ -454,18 +567,25 @@ export class DeleteTagGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing const taguuid = request.body.uuid || request.param.uuid; if (apiKey) { return this.missionGuardService.canKeyTagMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument taguuid, AccessGroupRights.DELETE, ); } return this.missionGuardService.canTagMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument taguuid, AccessGroupRights.WRITE, ); @@ -483,20 +603,29 @@ export class MoveMissionToProjectGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUID = request.query.missionUUID; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const projectUUID = request.query.projectUUID; if (apiKey) { throw new UnauthorizedException('CLI Keys cannot move missions'); } return ( (await this.projectGuardService.canAccessProject( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument projectUUID, AccessGroupRights.CREATE, )) && (await this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.DELETE, )) @@ -514,18 +643,26 @@ export class ReadFileGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const fileUUID = request.query.uuid ?? request.params.uuid; if (apiKey) { return this.fileGuardService.canKeyAccessFile( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument fileUUID, AccessGroupRights.READ, ); } return this.fileGuardService.canAccessFile( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument fileUUID, AccessGroupRights.READ, ); @@ -542,17 +679,25 @@ export class ReadFileByNameGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const filename = request.query.name; if (apiKey) { return this.fileGuardService.canKeyAccessFileByName( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument filename, AccessGroupRights.READ, ); } return this.fileGuardService.canAccessFileByName( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument filename, AccessGroupRights.READ, ); @@ -569,17 +714,26 @@ export class WriteFileGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); - const fileUUID = request.query.uuid || request.body.uuid; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const fileUUID = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing + request.query.uuid || request.body.uuid || request.params.uuid; if (apiKey) { return this.fileGuardService.canKeyAccessFile( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument fileUUID, AccessGroupRights.WRITE, ); } return this.fileGuardService.canAccessFile( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument fileUUID, AccessGroupRights.WRITE, ); @@ -597,22 +751,32 @@ export class MoveFilesGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const fileUUIDs = request.body.fileUUIDs; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUID = request.body.missionUUID; if (apiKey) { throw new UnauthorizedException('CLI Keys cannot move files'); } const canDeleteFiles = await this.fileGuardService.canAccessFiles( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument fileUUIDs, AccessGroupRights.DELETE, ); + if (!canDeleteFiles) { return false; } return this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, AccessGroupRights.CREATE, ); @@ -626,15 +790,23 @@ export class ReadActionGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const actionUUID = request.query.uuid; if (apiKey) { return this.actionGuardService.canKeyAccessAction( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument actionUUID, AccessGroupRights.READ, ); } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return this.actionGuardService.canAccessAction(user, actionUUID); } } @@ -650,23 +822,33 @@ export class CreateActionGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUID = request.body.missionUUID; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const actionTemplateUUID = request.body.templateUUID; const actionTemplate = await this.actionTemplateRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment where: { uuid: actionTemplateUUID }, }); if (apiKey) { return this.missionGuardService.canKeyAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, actionTemplate.accessRights, ); } return this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument missionUUID, actionTemplate.accessRights, ); @@ -685,9 +867,13 @@ export class DeleteActionGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const actionUUID = request.body.actionUUID; const action = await this.actionRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment where: { uuid: actionUUID }, relations: ['mission', 'creator'], }); @@ -713,12 +899,14 @@ export class DeleteActionGuard extends BaseGuard { "can't delete action unless its DONE, FAILED or UNPROCESSABLE", ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (action.creator.uuid === user.uuid) { return true; } const missionUUID = action.mission.uuid; return this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, missionUUID, AccessGroupRights.DELETE, @@ -738,17 +926,25 @@ export class CreateActionsGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUUIDs = request.body.missionUUIDs; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const actionTemplateUUID = request.body.templateUUID; const actionTemplate = await this.actionTemplateRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment where: { uuid: actionTemplateUUID }, }); if (apiKey) { const allCanAccess = await Promise.all( - missionUUIDs.map((missionUUID) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + missionUUIDs.map((missionUUID: string) => this.missionGuardService.canKeyAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument apiKey, missionUUID, actionTemplate.accessRights, @@ -758,9 +954,12 @@ export class CreateActionsGuard extends BaseGuard { return allCanAccess.every(Boolean); } const allCanAccess = await Promise.all( - missionUUIDs.map((missionUUID) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + missionUUIDs.map((missionUUID: string) => this.missionGuardService.canAccessMission( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access user.uuid, + missionUUID, actionTemplate.accessRights, ), @@ -780,6 +979,7 @@ export class IsAccessGroupCreatorByProjectAccessGuard extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); if (apiKey) { @@ -787,10 +987,15 @@ export class IsAccessGroupCreatorByProjectAccessGuard extends BaseGuard { 'CLI Keys cannot check access group creator', ); } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const projectAccessUUID = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing request.body.projectAccessUUID || request.params.projectAccessUUID; return this.authGuardService.canEditAccessGroupByProjectUuid( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument projectAccessUUID, ); } @@ -806,6 +1011,7 @@ export class CanEditGroupByGroupUuid extends BaseGuard { } async canActivate(context: ExecutionContext): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { user, apiKey, request } = await this.getUser(context); if (apiKey) { @@ -814,9 +1020,12 @@ export class CanEditGroupByGroupUuid extends BaseGuard { ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing const aguUUID = request.body.uuid || request.params.uuid; return this.authGuardService.canEditAccessGroupByGroupUuid( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument user, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument aguUUID, ); } diff --git a/backend/src/endpoints/category/category.controller.ts b/backend/src/endpoints/category/category.controller.ts index ff1c49770..d1907423e 100644 --- a/backend/src/endpoints/category/category.controller.ts +++ b/backend/src/endpoints/category/category.controller.ts @@ -1,16 +1,9 @@ -import { CategoriesDto } from '@common/api/types/category.dto'; +import { ApiOkResponse, OutputDto } from '@/decorators'; +import { CategoryService } from '@/services/category.service'; +import { QueryOptionalString, QueryUUID } from '@/validation/query-decorators'; +import { CategoriesDto } from '@kleinkram/api-dto'; +import { BodyString, BodyUUID, BodyUUIDArray } from '@kleinkram/validation'; import { Controller, Get, Post } from '@nestjs/common'; -import { ApiOkResponse, OutputDto } from '../../decarators'; -import { CategoryService } from '../../services/category.service'; -import { - BodyString, - BodyUUID, - BodyUUIDArray, -} from '../../validation/body-decorators'; -import { - QueryOptionalString, - QueryUUID, -} from '../../validation/query-decorators'; import { AddUser, AuthHeader } from '../auth/parameter-decorator'; import { CanCreateInProjectByBody, diff --git a/backend/src/endpoints/category/category.module.ts b/backend/src/endpoints/category/category.module.ts index 7f328ea63..48c3e53e2 100644 --- a/backend/src/endpoints/category/category.module.ts +++ b/backend/src/endpoints/category/category.module.ts @@ -1,9 +1,9 @@ -import CategoryEntity from '@common/entities/category/category.entity'; -import FileEntity from '@common/entities/file/file.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; +import { CategoryService } from '@/services/category.service'; +import { CategoryEntity } from '@kleinkram/backend-common/entities/category/category.entity'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { CategoryService } from '../../services/category.service'; import { CategoryController } from './category.controller'; @Module({ @@ -14,4 +14,5 @@ import { CategoryController } from './category.controller'; controllers: [CategoryController], exports: [CategoryService], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class CategoryModule {} diff --git a/backend/src/endpoints/file/file.controller.ts b/backend/src/endpoints/file/file.controller.ts index c45541eab..176a4758b 100644 --- a/backend/src/endpoints/file/file.controller.ts +++ b/backend/src/endpoints/file/file.controller.ts @@ -1,16 +1,60 @@ -import { CreatePreSignedURLSDto } from '@common/api/types/create-pre-signed-url.dto'; +import { ApiOkResponse, OutputDto } from '@/decorators'; +import { FileService } from '@/services/file.service'; +import { QueueService } from '@/services/queue.service'; import { + QueryBoolean, + QueryDate, + QueryOptionalDate, + QueryOptionalRecord, + QueryOptionalString, + QueryOptionalUUID, + QuerySkip, + QuerySortBy, + QuerySortDirection, + QueryString, + QueryTake, + QueryUUID, +} from '@/validation/query-decorators'; +import { + CancelFileUploadDto, + CancelProcessingResponseDto, + CancelUploadResponseDto, + ConfirmUploadDto, + DeleteFileResponseDto, + DeleteMissionResponseDto, + DownloadResponseDto, + DriveCreate, + DriveImportResponseDto, + FileDto, + FileEventsDto, FileExistsResponseDto, + FileQueryDto, + FileQueueEntriesDto, + FileQueueEntryDto, + FileWithTopicDto, + FilesDto, + FoxgloveLinkResponseDto, + IsUploadingDto, + MoveFilesResponseDto, + NoQueryParametersDto, + RecalculateHashesResponseDto, + ReextractTopicsResponseDto, + StopJobResponseDto, + StorageOverviewDto, + TemporaryAccessRequestDto, TemporaryFileAccessesDto, -} from '@common/api/types/file/access.dto'; -import { FileWithTopicDto } from '@common/api/types/file/file.dto'; -import { FilesDto } from '@common/api/types/file/files.dto'; -import { IsUploadingDto } from '@common/api/types/file/is-uploading.dto'; -import { NoQueryParametersDto } from '@common/api/types/no-query-parameters.dto'; -import { StorageOverviewDto } from '@common/api/types/storage-overview.dto'; -import { UpdateFile } from '@common/api/types/update-file.dto'; -import env from '@common/environment'; + UpdateFile, +} from '@kleinkram/api-dto'; +import env from '@kleinkram/backend-common/environment'; import { + BodyOptionalSource, + BodyString, + BodyUUID, + BodyUUIDArray, + isValidFileName, +} from '@kleinkram/validation'; +import { + BadRequestException, Body, Controller, Delete, @@ -19,24 +63,9 @@ import { Put, Query, } from '@nestjs/common'; -import { ApiOkResponse, OutputDto } from '../../decarators'; +import { plainToInstance } from 'class-transformer'; import logger from '../../logger'; -import { FileService } from '../../services/file.service'; -import { BodyUUID, BodyUUIDArray } from '../../validation/body-decorators'; import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; -import { - QueryBoolean, - QueryOptionalDate, - QueryOptionalRecord, - QueryOptionalString, - QueryOptionalUUID, - QuerySkip, - QuerySortBy, - QuerySortDirection, - QueryString, - QueryTake, - QueryUUID, -} from '../../validation/query-decorators'; import { AddUser, AuthHeader } from '../auth/parameter-decorator'; import { AdminOnly, @@ -51,15 +80,16 @@ import { UserOnly, } from '../auth/roles.decorator'; -import { CancelFileUploadDto } from '@common/api/types/cancel-file-upload.dto'; -import { FileEventsDto } from '@common/api/types/file/file-event.dto'; -import { FileQueryDto } from '@common/api/types/file/file-query.dto'; -import FileEntity from '@common/entities/file/file.entity'; -import { HealthStatus } from '@common/frontend_shared/enum'; +import { FoxgloveService } from '@/services/foxglove.service'; +import { FileSource, HealthStatus } from '@kleinkram/shared'; -@Controller(['file', 'files']) // TODO: migrate to 'files' +@Controller(['files']) export class FileController { - constructor(private readonly fileService: FileService) {} + constructor( + private readonly fileService: FileService, + private readonly queueService: QueueService, + private readonly foxgloveService: FoxgloveService, + ) {} @Get() @LoggedIn() @@ -71,6 +101,22 @@ export class FileController { @Query() query: FileQueryDto, @AddUser() auth: AuthHeader, ): Promise { + // we pre-check the access to give a proper error message + // the actual findMany method will check access again per file + await this.fileService.checkResourceAccess( + query.projectUuids ?? [], + query.missionUuids ?? [], + auth.user.uuid, + ); + + // also check access by patterns + await this.fileService.checkResourceAccessByName( + query.projectPatterns ?? [], + query.missionPatterns ?? [], + auth.user.uuid, + ); + + // now fetch files, we only query files we have access to return await this.fileService.findMany( query.projectUuids ?? [], query.projectPatterns ?? [], @@ -127,7 +173,7 @@ export class FileController { 'Returned File needs all specified topics (true) or any specified topics (false)', ) matchAllTopics: boolean, - @QueryOptionalRecord('tags', 'Dictionary Tagtype name to Tag value') + @QueryOptionalRecord('tags', 'Dictionary Tagtype name to Tag value') // eslint-disable-next-line @typescript-eslint/no-explicit-any tags: Record, @QuerySkip('skip') skip: number, @QueryTake('take') take: number, @@ -137,8 +183,8 @@ export class FileController { @AddUser() auth: AuthHeader, ): Promise { let _missionUUID = missionUUID; - if (auth.apikey) { - _missionUUID = auth.apikey.mission.uuid; + if (auth.apiKey) { + _missionUUID = auth.apiKey.mission.uuid; } return await this.fileService.findFiltered( fileName, @@ -162,7 +208,10 @@ export class FileController { @Get('download') @CanReadFile() - @OutputDto(null) // TODO: type API response + @ApiOkResponse({ + description: 'Download link', + type: DownloadResponseDto, + }) async download( @QueryUUID('uuid', 'File UUID') uuid: string, @QueryBoolean( @@ -175,16 +224,18 @@ export class FileController { 'preview_only', 'Whether the download link is for preview only (true) or full download (false)', ) - preview_only = false, + previewOnly = false, @AddUser() auth: AuthHeader, - ): Promise { + ): Promise { logger.debug(`download ${uuid}: expires=${expires.toString()}`); - return this.fileService.generateDownload( + const url = await this.fileService.generateDownload( uuid, expires, - preview_only, + previewOnly, auth.user, + auth.apiKey?.action, ); + return { url }; } // TODO: replace this with /file/:uuid @@ -197,46 +248,80 @@ export class FileController { async getFileById( @QueryUUID('uuid', 'File UUID') uuid: string, ): Promise { - return this.fileService.findOne(uuid); + const file = await this.fileService.findOne(uuid); + return plainToInstance(FileWithTopicDto, file, { + excludeExtraneousValues: true, + }); } @Put(':uuid') @CanWriteFile() - @OutputDto(null) // TODO: type API response + @ApiOkResponse({ + description: 'File', + type: FileDto, + }) async update( @ParameterUID('uuid') uuid: string, @Body() dto: UpdateFile, @AddUser() auth: AuthHeader, - ): Promise { - return this.fileService.update(uuid, dto, auth.user); + ): Promise { + const file = await this.fileService.update( + uuid, + dto, + auth.user, + auth.apiKey?.action, + ); + return plainToInstance(FileDto, file, { + excludeExtraneousValues: true, + }); } @Post('moveFiles') @CanMoveFiles() - @OutputDto(null) // TODO: type API response + @ApiOkResponse({ + description: 'Move Files Response', + type: MoveFilesResponseDto, + }) async moveFiles( @BodyUUIDArray('fileUUIDs', 'List of File UUID to be moved') fileUUIDs: string[], @BodyUUID('missionUUID', 'UUID of target Mission') missionUUID: string, - ): Promise { - return this.fileService.moveFiles(fileUUIDs, missionUUID); + @AddUser() auth: AuthHeader, + ): Promise { + await this.fileService.moveFiles( + fileUUIDs, + missionUUID, + auth.user, + auth.apiKey?.action, + ); + return { success: true }; } @Get('oneByName') @CanReadMission() - @OutputDto(null) // TODO: type API response + @ApiOkResponse({ + description: 'File', + type: FileDto, + }) async getOneFileByName( @QueryUUID('uuid', 'Mission UUID to search in') uuid: string, @QueryString('filename', 'Filename searched for') name: string, - ): Promise { - return this.fileService.findOneByName(uuid, name); + ): Promise { + const file = await this.fileService.findOneByName(uuid, name); + return plainToInstance(FileDto, file, { + excludeExtraneousValues: true, + }); } @Delete(':uuid') @CanDeleteFile() - @OutputDto(null) // TODO: type API response - async deleteFile(@ParameterUID('uuid') uuid: string): Promise { - await this.fileService.deleteFile(uuid); + @OutputDto(DeleteFileResponseDto) + async deleteFile( + @ParameterUID('uuid') uuid: string, + @AddUser() auth: AuthHeader, + ): Promise { + await this.fileService.deleteFile(uuid, auth.user, auth.apiKey?.action); + return { success: true }; } @Get('storage') @@ -266,42 +351,79 @@ export class FileController { @Post('temporaryAccess') @CanCreateInMissionByBody() - @OutputDto(null) // TODO: type API response + @ApiOkResponse({ + description: 'Temporary file access', + type: TemporaryFileAccessesDto, + }) async getTemporaryAccess( @AddUser() auth: AuthHeader, - @Body() body: CreatePreSignedURLSDto, + @Body() body: TemporaryAccessRequestDto, ): Promise { + let source = body.source; + if (!source) { + source = FileSource.WEB_INTERFACE; + if (auth.apiKey) { + source = auth.apiKey.action + ? FileSource.ACTION + : FileSource.CLI; + } + } + + const invalidFiles: { filename: string; error: string }[] = []; + for (const filename of body.filenames) { + if (!isValidFileName(filename)) { + invalidFiles.push({ + filename, + error: `Filename "${filename}" is not valid!`, + }); + } + } + + if (invalidFiles.length > 0) { + throw new BadRequestException({ + message: 'Validation failed', + errors: invalidFiles, + }); + } + return await this.fileService.getTemporaryAccess( body.filenames, body.missionUUID, auth.user.uuid, + auth.apiKey?.action, + source, ); } @Post('cancelUpload') @UserOnly() //Push back authentication to the queue to accelerate the request - @OutputDto(null) // TODO: type API response + @OutputDto(CancelUploadResponseDto) async cancelUpload( @Body() dto: CancelFileUploadDto, @AddUser() auth: AuthHeader, - ): Promise { - logger.debug(`cancelUpload ${dto.uuids.toString()}`); + ): Promise { + logger.debug(`cancelUpload ${JSON.stringify(dto)}`); await this.fileService.cancelUpload( dto.uuids, dto.missionUuid, auth.user.uuid, ); + return { success: true }; } @Post('deleteMultiple') @CanDeleteMission() - @OutputDto(null) // TODO: type API response + @ApiOkResponse({ + description: 'Delete Files Response', + type: DeleteFileResponseDto, + }) async deleteMultiple( @BodyUUIDArray('uuids', 'List of File UUID to be deleted') uuids: string[], @BodyUUID('missionUUID', 'Mission UUID') missionUUID: string, - ): Promise { - return this.fileService.deleteMultiple(uuids, missionUUID); + ): Promise { + await this.fileService.deleteMultiple(uuids, missionUUID); + return { success: true }; } @Get('exists') @@ -352,10 +474,158 @@ export class FileController { @Post('reextractTopics') @AdminOnly() - @OutputDto(null) // TODO: type API response - async reextractTopics(): Promise<{ count: number }> { + @ApiOkResponse({ + description: 'Reextracting topics completed', + type: ReextractTopicsResponseDto, + }) + async reextractTopics(): Promise { logger.debug('Triggering manual topic extraction for missing files'); const count = await this.fileService.reextractMissingTopics(); return { count }; } + + @Get(':uuid/foxglove-link') + @CanReadFile() + @ApiOkResponse({ + description: 'Generates a signed link for Foxglove Studio.', + type: FoxgloveLinkResponseDto, + }) + async getFoxgloveLink( + @ParameterUID('uuid') uuid: string, + @AddUser() auth: AuthHeader, + ): Promise<{ url: string }> { + const url = await this.foxgloveService.generateFoxgloveUrl( + uuid, + auth.user, + ); + return { url }; + } + + @Post('import/drive') + @CanCreateInMissionByBody() + @OutputDto(DriveImportResponseDto) + async importFromDrive( + @Body() body: DriveCreate, + @AddUser() authHeader: AuthHeader, + ): Promise { + await this.queueService.importFromDrive(body, authHeader.user); + return { + success: true, + }; + } + + @Post('upload/confirm') + @LoggedIn() + @ApiOkResponse({ + type: ConfirmUploadDto, + }) + async confirmUpload( + @BodyUUID('uuid', 'File UUID of file that successfully uploaded') + uuid: string, + @BodyString('md5', 'MD5 hash to validate uncorrupted upload') + md5: string, + @BodyOptionalSource( + 'source', + 'Source of the upload (CLI, Web Interface, etc.)', + ) + source: FileSource | undefined, + @AddUser() auth: AuthHeader, + ): Promise { + let _source = source; + if (!_source) { + _source = FileSource.WEB_INTERFACE; + if (auth.apiKey) { + _source = auth.apiKey.action + ? FileSource.ACTION + : FileSource.CLI; + } + } + await this.queueService.confirmUpload(uuid, md5, auth.user, _source); + return { + success: true, + }; + } + + @Post('maintenance/recalculate-hashes') + @AdminOnly() + @ApiOkResponse({ + description: 'Recalculating hashes completed', + type: RecalculateHashesResponseDto, + }) + async recalculateHashes(): Promise { + return await this.queueService.recalculateHashes(); + } + + @Get('queue') + @LoggedIn() + @ApiOkResponse({ + type: FileQueueEntriesDto, + }) + async active( + @QueryDate('startDate', 'Start of time range to filter queue by') + startDate: string, + @QueryOptionalString('stateFilter', 'State of QueueEntity to filter by') + stateFilter: string, + @QuerySkip('skip') skip: number, + @QueryTake('take') take: number, + @AddUser() user: AuthHeader, + ): Promise { + const date = new Date(startDate); + + const data = (await this.queueService.active( + date, + stateFilter, + user.user.uuid, + skip, + take, + )) as unknown as FileQueueEntryDto[]; + + return plainToInstance( + FileQueueEntriesDto, + { + data, + // TODO: implenment count in queue Service + count: data.length, + skip, + take, + }, + { excludeExtraneousValues: true }, + ); + } + + @Delete('queue/:uuid') + @CanDeleteMission() + @ApiOkResponse({ + type: DeleteMissionResponseDto, + }) + async deleteQueueItem( + @BodyUUID('missionUUID', 'Mission UUID') missionUUID: string, + @ParameterUID('uuid') uuid: string, + ): Promise { + return this.queueService.delete(missionUUID, uuid); + } + + @Post('queue/:uuid/cancel') + @CanDeleteMission() + @ApiOkResponse({ + type: CancelProcessingResponseDto, + }) + async cancelProcessing( + @ParameterUID('uuid') queueUUID: string, + @BodyUUID('missionUUID', 'Mission UUID of Queue') missionUUID: string, + ): Promise { + return this.queueService.cancelProcessing(queueUUID, missionUUID); + } + + @Post('queue/:uuid/stop') + @CanDeleteMission() + @ApiOkResponse({ + type: StopJobResponseDto, + }) + async stopJob( + @ParameterUID('uuid') queueUUID: string, + ): Promise { + await this.queueService.stopJob(queueUUID); + return { success: true }; + } } diff --git a/backend/src/endpoints/file/file.module.ts b/backend/src/endpoints/file/file.module.ts index eef1694c3..7c33141ea 100644 --- a/backend/src/endpoints/file/file.module.ts +++ b/backend/src/endpoints/file/file.module.ts @@ -1,26 +1,26 @@ -import FileEntity from '@common/entities/file/file.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import TopicEntity from '@common/entities/topic/topic.entity'; +import { FileGuardService } from '@/services/file-guard.service'; +import { FileService } from '@/services/file.service'; +import { MissionService } from '@/services/mission.service'; +import { TagService } from '@/services/tag.service'; +import { TopicService } from '@/services/topic.service'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { AccountEntity } from '@kleinkram/backend-common/entities/auth/account.entity'; +import { CategoryEntity } from '@kleinkram/backend-common/entities/category/category.entity'; +import { FileEventEntity } from '@kleinkram/backend-common/entities/file/file-event.entity'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { IngestionJobEntity } from '@kleinkram/backend-common/entities/file/ingestion-job.entity'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { TagTypeEntity } from '@kleinkram/backend-common/entities/tagType/tag-type.entity'; +import { TopicEntity } from '@kleinkram/backend-common/entities/topic/topic.entity'; +import { StorageModule } from '@kleinkram/backend-common/modules/storage/storage.module'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { FileService } from '../../services/file.service'; -import { TopicService } from '../../services/topic.service'; +import { FoxgloveModule } from '../integrations/foxglove.module'; +import { QueueModule } from '../queue/queue.module'; import { FileController } from './file.controller'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; -import { MissionService } from '../../services/mission.service'; -import { TagService } from '../../services/tag.service'; - -import CategoryEntity from '@common/entities/category/category.entity'; -import FileEventEntity from '@common/entities/file/file-event.entity'; -import IngestionJobEntity from '@common/entities/file/ingestion-job.entity'; -import { StorageModule } from '@common/modules/storage/storage.module'; -import { FileGuardService } from '../../services/file-guard.service'; - @Module({ imports: [ TypeOrmModule.forFeature([ @@ -37,6 +37,8 @@ import { FileGuardService } from '../../services/file-guard.service'; FileEventEntity, ]), StorageModule, + FoxgloveModule, + QueueModule, ], providers: [ FileService, @@ -48,4 +50,5 @@ import { FileGuardService } from '../../services/file-guard.service'; controllers: [FileController], exports: [FileService], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class FileModule {} diff --git a/backend/src/endpoints/health/health.controller.ts b/backend/src/endpoints/health/health.controller.ts new file mode 100644 index 000000000..420194240 --- /dev/null +++ b/backend/src/endpoints/health/health.controller.ts @@ -0,0 +1,13 @@ +import { OutputDto } from '@/decorators'; +import { Controller, Get } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +@ApiTags('health') +@Controller('api/health') +export class HealthController { + @Get() + @OutputDto(null) + check() { + return { status: 'ok' }; + } +} diff --git a/backend/src/endpoints/health/health.module.ts b/backend/src/endpoints/health/health.module.ts new file mode 100644 index 000000000..5c40be938 --- /dev/null +++ b/backend/src/endpoints/health/health.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { HealthController } from './health.controller'; + +@Module({ + controllers: [HealthController], +}) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class HealthModule {} diff --git a/backend/src/endpoints/integrations/foxglove.controller.ts b/backend/src/endpoints/integrations/foxglove.controller.ts new file mode 100644 index 000000000..9679368a4 --- /dev/null +++ b/backend/src/endpoints/integrations/foxglove.controller.ts @@ -0,0 +1,54 @@ +import { OutputDto } from '@/decorators'; +import { FoxgloveService } from '@/services/foxglove.service'; +import { ParameterUuid } from '@/validation/parameter-decorators'; +import { Controller, Get, Query, Req, Res } from '@nestjs/common'; +import { ApiResponse } from '@nestjs/swagger'; +import { Request, Response } from 'express'; + +@Controller('integrations/foxglove') +export class FoxgloveController { + // Whitelist for this specific controller + // see https://docs.foxglove.dev/docs/visualization/connecting/live-data#cross-origin-resource-sharing-cors-setup + private readonly allowedOrigins = new Set([ + 'https://app.foxglove.dev', + 'https://embed.foxglove.dev', + ]); + + constructor(private readonly foxgloveService: FoxgloveService) {} + + @Get(':uuid/:filename') + @ApiResponse({ + description: '302 Redirect to actual file URL', + status: 302, + }) + @OutputDto(null) // No response body, just a redirect + // No @LoggedIn() guard here! The signature IS the guard. + async proxyFoxglove( + @ParameterUuid('uuid') uuid: string, + @Query('expires') expires: string, + @Query('signature') signature: string, + @Query('u') userUuid: string, + @Res() response: Response, + @Req() request: Request, + ): Promise { + const resolvedFileUrl = await this.foxgloveService.resolveRedirectUrl( + uuid, + Number(expires), + signature, + userUuid, + ); + + const origin = request.headers.origin; + if (origin && this.allowedOrigins.has(origin)) { + response.header('Access-Control-Allow-Origin', origin); + } else { + response.header('Access-Control-Allow-Origin', 'null'); + } + + response.header('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS'); + response.header('Access-Control-Allow-Credentials', 'true'); + + // 302 Redirect to MinIO/S3 + response.redirect(resolvedFileUrl); + } +} diff --git a/backend/src/endpoints/integrations/foxglove.module.ts b/backend/src/endpoints/integrations/foxglove.module.ts new file mode 100644 index 000000000..44f2b889f --- /dev/null +++ b/backend/src/endpoints/integrations/foxglove.module.ts @@ -0,0 +1,20 @@ +import { FileGuardService } from '@/services/file-guard.service'; +import { FoxgloveService } from '@/services/foxglove.service'; +import { FileEventEntity } from '@kleinkram/backend-common/entities/file/file-event.entity'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { StorageModule } from '@kleinkram/backend-common/modules/storage/storage.module'; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { FoxgloveController } from './foxglove.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([FileEntity, FileEventEntity]), + StorageModule, + ], + providers: [FoxgloveService, FileGuardService], + controllers: [FoxgloveController], + exports: [FoxgloveService], +}) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class FoxgloveModule {} diff --git a/backend/src/endpoints/mission/mission.controller.ts b/backend/src/endpoints/mission/mission.controller.ts index 8477241c5..5d244b368 100644 --- a/backend/src/endpoints/mission/mission.controller.ts +++ b/backend/src/endpoints/mission/mission.controller.ts @@ -1,15 +1,5 @@ -import { CreateMission } from '@common/api/types/create-mission.dto'; -import { - FlatMissionDto, - MinimumMissionsDto, - MissionsDto, - MissionWithFilesDto, -} from '@common/api/types/mission/mission.dto'; -import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common'; -import { ApiOkResponse, OutputDto } from '../../decarators'; -import { MissionService } from '../../services/mission.service'; -import { BodyUUID } from '../../validation/body-decorators'; -import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; +import { ApiOkResponse, OutputDto } from '@/decorators'; +import { MissionService } from '@/services/mission.service'; import { QueryOptionalString, QuerySkip, @@ -17,8 +7,17 @@ import { QuerySortDirection, QueryTake, QueryUUID, -} from '../../validation/query-decorators'; -import { MISSION_NAME_REGEX } from '../../validation/validation-logic'; +} from '@/validation/query-decorators'; +import { + CreateMission, + FlatMissionDto, + MinimumMissionsDto, + MissionsDto, + MissionWithFilesDto, +} from '@kleinkram/api-dto'; +import { BodyUUID, MISSION_NAME_REGEX } from '@kleinkram/validation'; +import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common'; +import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; import { CanCreateInProjectByBody, CanDeleteMission, @@ -28,7 +27,7 @@ import { UserOnly, } from '../auth/roles.decorator'; -import { MissionQueryDto } from '@common/api/types/mission/mission-query.dto'; +import { MissionQueryDto } from '@kleinkram/api-dto'; import { AddUser, AuthHeader } from '../auth/parameter-decorator'; diff --git a/backend/src/endpoints/mission/mission.module.ts b/backend/src/endpoints/mission/mission.module.ts index 37d494ed2..2319c7510 100644 --- a/backend/src/endpoints/mission/mission.module.ts +++ b/backend/src/endpoints/mission/mission.module.ts @@ -1,15 +1,15 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; -import { StorageModule } from '@common/modules/storage/storage.module'; +import { MissionService } from '@/services/mission.service'; +import { TagService } from '@/services/tag.service'; +import { UserService } from '@/services/user.service'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { AccountEntity } from '@kleinkram/backend-common/entities/auth/account.entity'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { TagTypeEntity } from '@kleinkram/backend-common/entities/tagType/tag-type.entity'; +import { StorageModule } from '@kleinkram/backend-common/modules/storage/storage.module'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { MissionService } from '../../services/mission.service'; -import { TagService } from '../../services/tag.service'; -import { UserService } from '../../services/user.service'; import { MissionController } from './mission.controller'; @Module({ @@ -28,4 +28,5 @@ import { MissionController } from './mission.controller'; controllers: [MissionController], exports: [MissionService], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class MissionModule {} diff --git a/backend/src/endpoints/project/project.controller.ts b/backend/src/endpoints/project/project.controller.ts index 911a2f9ca..617047615 100644 --- a/backend/src/endpoints/project/project.controller.ts +++ b/backend/src/endpoints/project/project.controller.ts @@ -1,18 +1,24 @@ -import { DefaultRights } from '@common/api/types/access-control/default-rights'; +import { ApiOkResponse, ApiResponse, OutputDto } from '@/decorators'; +import { AccessService } from '@/services/access.service'; +import { ProjectService } from '@/services/project.service'; +import { ParameterUuid as ParameterUID } from '@/validation/parameter-decorators'; +import { QueryTake, QueryUUID } from '@/validation/query-decorators'; import { + AddTagTypeDto, + CreateProject, + DefaultRights, + DeleteProjectResponseDto, ProjectAccessDto, ProjectAccessListDto, -} from '@common/api/types/access-control/project-access.dto'; -import { AddTagTypeDto } from '@common/api/types/add-tag-type.dto'; -import { CreateProject } from '@common/api/types/create-project.dto'; -import { ProjectDto } from '@common/api/types/project/base-project.dto'; -import { DeleteProjectResponseDto } from '@common/api/types/project/delete-project-response.dto'; -import { ProjectQueryDto } from '@common/api/types/project/project-query.dto'; -import { ProjectWithRequiredTagsDto } from '@common/api/types/project/project-with-required-tags.dto'; -import { ProjectsDto } from '@common/api/types/project/projects.dto'; -import { ResentProjectsDto } from '@common/api/types/project/recent-projects.dto'; -import { RemoveTagTypeDto } from '@common/api/types/remove-tag-type.dto'; -import { UpdateTagTypesDto } from '@common/api/types/update-tag-types.dto'; + ProjectDto, + ProjectQueryDto, + ProjectsDto, + ProjectWithRequiredTagsDto, + RemoveTagTypeDto, + ResentProjectsDto, + UpdateTagTypesDto, +} from '@kleinkram/api-dto'; +import { BodyUUIDArray } from '@kleinkram/validation'; import { Body, Controller, @@ -24,12 +30,6 @@ import { Query, } from '@nestjs/common'; import { ApiOperation } from '@nestjs/swagger'; -import { ApiOkResponse, ApiResponse, OutputDto } from '../../decarators'; -import { AccessService } from '../../services/access.service'; -import { ProjectService } from '../../services/project.service'; -import { BodyUUIDArray } from '../../validation/body-decorators'; -import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; -import { QueryTake, QueryUUID } from '../../validation/query-decorators'; import { AddUser, AuthHeader } from '../auth/parameter-decorator'; import { CanCreate, @@ -237,7 +237,7 @@ export class OldProjectController { @LoggedIn() @ApiOperation({ summary: 'Get default rights', - description: `Get the default rights for a project, the default rights + description: `Get the default rights for a project, the default rights are the rights that should be assigned to a new project upon creation`, }) @ApiOkResponse({ diff --git a/backend/src/endpoints/project/project.module.ts b/backend/src/endpoints/project/project.module.ts index 1a3895a8d..9f2779521 100644 --- a/backend/src/endpoints/project/project.module.ts +++ b/backend/src/endpoints/project/project.module.ts @@ -1,12 +1,11 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import ProjectAccessEntity from '@common/entities/auth/project-access.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; +import { AccessService } from '@/services/access.service'; +import { ProjectService } from '@/services/project.service'; +import { AccessGroupEntity, ProjectEntity } from '@kleinkram/backend-common'; +import { AccountEntity } from '@kleinkram/backend-common/entities/auth/account.entity'; +import { ProjectAccessEntity } from '@kleinkram/backend-common/entities/auth/project-access.entity'; +import { TagTypeEntity } from '@kleinkram/backend-common/entities/tagType/tag-type.entity'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AccessService } from '../../services/access.service'; -import { ProjectService } from '../../services/project.service'; import { OldProjectController, ProjectController } from './project.controller'; @Module({ @@ -23,4 +22,5 @@ import { OldProjectController, ProjectController } from './project.controller'; exports: [ProjectService], controllers: [ProjectController, OldProjectController], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class ProjectModule {} diff --git a/backend/src/endpoints/queue/queue.controller.ts b/backend/src/endpoints/queue/queue.controller.ts deleted file mode 100644 index 717517ff9..000000000 --- a/backend/src/endpoints/queue/queue.controller.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { CancelProcessingResponseDto } from '@common/api/types/cancel-processing-response.dto'; -import { ConfirmUploadDto } from '@common/api/types/confirm-upload.dto'; -import { DeleteMissionResponseDto } from '@common/api/types/delete-mission-response.dto'; -import { DriveCreate } from '@common/api/types/drive-create.dto'; -import { QueueActiveDto } from '@common/api/types/queue-active.dto'; -import { UpdateTagTypeDto } from '@common/api/types/update-tag-type.dto'; -import { Body, Controller, Delete, Get, Post } from '@nestjs/common'; -import { ApiOkResponse, OutputDto } from '../../decarators'; -import QueueService from '../../services/queue.service'; -import { BodyString, BodyUUID } from '../../validation/body-decorators'; -import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; -import { - QueryDate, - QueryOptionalString, - QuerySkip, - QueryTake, -} from '../../validation/query-decorators'; -import { AddUser, AuthHeader } from '../auth/parameter-decorator'; -import { - AdminOnly, - CanCreateInMissionByBody, - CanDeleteMission, - LoggedIn, -} from '../auth/roles.decorator'; - -@Controller('queue') -export class QueueController { - constructor(private readonly queueService: QueueService) {} - - @Post('import_from_drive') - @CanCreateInMissionByBody() - @ApiOkResponse({ - type: UpdateTagTypeDto, - }) - async importFromDrive( - @Body() body: DriveCreate, - @AddUser() authHeader: AuthHeader, - ): Promise { - return this.queueService.importFromDrive(body, authHeader.user); - } - - @Post('confirmUpload') - @LoggedIn() - @ApiOkResponse({ - type: ConfirmUploadDto, - }) - async confirmUpload( - @BodyUUID('uuid', 'File UUID of file that successfully uploaded') - uuid: string, - @BodyString('md5', 'MD5 hash to validate uncorrupted upload') - md5: string, - @AddUser() auth: AuthHeader, - ): Promise { - await this.queueService.confirmUpload(uuid, md5, auth.user); - return { - success: true, - }; - } - - @Post('recalculateHashes') - @AdminOnly() - @OutputDto(null) - async recalculateHashes(): Promise<{ - success: boolean; - fileCount: number; - }> { - return await this.queueService.recalculateHashes(); - } - - @Get('active') - @LoggedIn() - @OutputDto(null) - async active( - @QueryDate('startDate', 'Start of time range to filter queue by') - startDate: string, - @QueryOptionalString('stateFilter', 'State of QueueEntity to filter by') - stateFilter: string, - @QuerySkip('skip') skip: number, - @QueryTake('take') take: number, - @AddUser() user: AuthHeader, - ): Promise { - const date = new Date(startDate); - - return this.queueService.active( - date, - stateFilter, - user.user.uuid, - skip, - take, - ); - } - - @Delete(':uuid') - @CanDeleteMission() - @ApiOkResponse({ - type: DeleteMissionResponseDto, - }) - async delete( - @BodyUUID('missionUUID', 'Mission UUID') missionUUID: string, - @ParameterUID('uuid') uuid: string, - ): Promise { - return this.queueService.delete(missionUUID, uuid); - } - - @Post('cancelProcessing') - @CanDeleteMission() - @ApiOkResponse({ - type: CancelProcessingResponseDto, - }) - async cancelProcessing( - @BodyUUID('queueUUID', 'Queue UUID to cancel') queueUUID: string, - @BodyUUID('missionUUID', 'Mission UUID of Queue') missionUUID: string, - ): Promise { - return this.queueService.cancelProcessing(queueUUID, missionUUID); - } - - /** - - @Get('bullQueue') - @AdminOnly() - @OutputDto(null) // TODO: type API response - async bullQueue(): Promise { - return this.queueService.bullQueue(); - } - - @Post('stopJob') - @AdminOnly() - @ApiOkResponse({ - type: StopJobResponseDto, - }) - async stopJob( - @BodyUUID('jobId', 'Bull ID of Job to cancel') jobId: string, - ): Promise { - return this.queueService.stopAction(jobId); - } - - */ -} diff --git a/backend/src/endpoints/queue/queue.module.ts b/backend/src/endpoints/queue/queue.module.ts index cb1310581..24ca5ef40 100644 --- a/backend/src/endpoints/queue/queue.module.ts +++ b/backend/src/endpoints/queue/queue.module.ts @@ -1,19 +1,18 @@ -import ActionEntity from '@common/entities/action/action.entity'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import FileEventEntity from '@common/entities/file/file-event.entity'; -import FileEntity from '@common/entities/file/file.entity'; -import IngestionJobEntity from '@common/entities/file/ingestion-job.entity'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import WorkerEntity from '@common/entities/worker/worker.entity'; -import { StorageModule } from '@common/modules/storage/storage.module'; +import QueueService from '@/services/queue.service'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; +import { AccountEntity } from '@kleinkram/backend-common/entities/auth/account.entity'; +import { FileEventEntity } from '@kleinkram/backend-common/entities/file/file-event.entity'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { IngestionJobEntity } from '@kleinkram/backend-common/entities/file/ingestion-job.entity'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { WorkerEntity } from '@kleinkram/backend-common/entities/worker/worker.entity'; +import { StorageModule } from '@kleinkram/backend-common/modules/storage/storage.module'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { makeGaugeProvider } from '@willsoto/nestjs-prometheus'; -import QueueService from '../../services/queue.service'; -import { QueueController } from './queue.controller'; @Module({ imports: [ @@ -59,7 +58,8 @@ import { QueueController } from './queue.controller'; labelNames: ['queue'], }), ], - controllers: [QueueController], + controllers: [], exports: [QueueService], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class QueueModule {} diff --git a/backend/src/endpoints/tag/tag.controller.ts b/backend/src/endpoints/tag/tag.controller.ts index 85e55ef4d..757f32f68 100644 --- a/backend/src/endpoints/tag/tag.controller.ts +++ b/backend/src/endpoints/tag/tag.controller.ts @@ -1,18 +1,21 @@ -import { AddTagsDto } from '@common/api/types/tags/add-tags.dto'; -import { CreateTagTypeDto } from '@common/api/types/tags/create-tag-type.dto'; -import { DeleteTagDto } from '@common/api/types/tags/delete-tag.dto'; -import { TagTypeDto, TagTypesDto } from '@common/api/types/tags/tags.dto'; -import { DataType } from '@common/frontend_shared/enum'; -import { Body, Controller, Delete, Get, Post } from '@nestjs/common'; -import { ApiOkResponse } from '../../decarators'; -import { TagService } from '../../services/tag.service'; -import { BodyNotNull, BodyUUID } from '../../validation/body-decorators'; -import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; +import { ApiOkResponse } from '@/decorators'; +import { TagService } from '@/services/tag.service'; import { QueryOptionalString, QuerySkip, QueryTake, -} from '../../validation/query-decorators'; +} from '@/validation/query-decorators'; +import { + AddTagsDto, + CreateTagTypeDto, + DeleteTagDto, + TagTypeDto, + TagTypesDto, +} from '@kleinkram/api-dto'; +import { DataType } from '@kleinkram/shared'; +import { BodyNotNull, BodyUUID } from '@kleinkram/validation'; +import { Body, Controller, Delete, Get, Post } from '@nestjs/common'; +import { ParameterUuid as ParameterUID } from '../../validation/parameter-decorators'; import { CanAddTag, CanCreate, diff --git a/backend/src/endpoints/tag/tag.module.ts b/backend/src/endpoints/tag/tag.module.ts index ead651a84..877f4ed29 100644 --- a/backend/src/endpoints/tag/tag.module.ts +++ b/backend/src/endpoints/tag/tag.module.ts @@ -1,13 +1,12 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import ApikeyEntity from '@common/entities/auth/apikey.entity'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; +import { TagService } from '@/services/tag.service'; +import { AccessGroupEntity, ApiKeyEntity } from '@kleinkram/backend-common'; +import { AccountEntity } from '@kleinkram/backend-common/entities/auth/account.entity'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { TagTypeEntity } from '@kleinkram/backend-common/entities/tagType/tag-type.entity'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { TagService } from '../../services/tag.service'; import { TagController } from './tag.controller'; @Module({ @@ -19,11 +18,12 @@ import { TagController } from './tag.controller'; AccessGroupEntity, ProjectEntity, AccountEntity, - ApikeyEntity, + ApiKeyEntity, ]), ], providers: [TagService], controllers: [TagController], exports: [TagService], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class TagModule {} diff --git a/backend/src/endpoints/templates/templates.controller.ts b/backend/src/endpoints/templates/templates.controller.ts new file mode 100644 index 000000000..f2114405c --- /dev/null +++ b/backend/src/endpoints/templates/templates.controller.ts @@ -0,0 +1,107 @@ +import { + ActionTemplateDto, + ActionTemplatesDto, + CreateTemplateDto, + UpdateTemplateDto, +} from '@kleinkram/api-dto'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Post, + Query, +} from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { ApiOkResponse, OutputDto } from '@/decorators'; +import { TemplateService } from '@/services/template.service'; +import { ParameterUuid } from '@/validation/parameter-decorators'; +import { QuerySkip, QueryTake } from '@/validation/query-decorators'; +import { ActionTemplateAvailabilityDto } from '@kleinkram/api-dto'; +import { AddUser, AuthHeader } from '../auth/parameter-decorator'; +import { CanCreate, LoggedIn } from '../auth/roles.decorator'; + +@ApiTags('Templates') +@Controller('templates') +export class TemplatesController { + constructor(private readonly templateService: TemplateService) {} + + @Post() + @CanCreate() + @ApiOperation({ summary: 'Create a new action template' }) + @ApiOkResponse({ type: ActionTemplateDto }) + async createNewTemplate( + @Body() dto: CreateTemplateDto, + @AddUser() user: AuthHeader, + ): Promise { + return this.templateService.create(dto, user); + } + + @Post(':uuid/versions') + @CanCreate() + @ApiOperation({ summary: 'Create a new version of an existing template' }) + @ApiOkResponse({ type: ActionTemplateDto }) + async createNewTemplateVersion( + @ParameterUuid('uuid') uuid: string, + @Body() dto: UpdateTemplateDto, + @AddUser() user: AuthHeader, + ): Promise { + return this.templateService.createVersion(uuid, dto, user); + } + + @Get() + @LoggedIn() + @ApiOperation({ summary: 'List action templates' }) + @ApiOkResponse({ + description: 'List of action templates', + type: ActionTemplatesDto, + }) + async findAllTemplates( + @QuerySkip('skip') skip: number, + @QueryTake('take') take: number, + @Query('search') search?: string, + @Query('includeArchived') includeArchived?: boolean, + ): Promise { + return this.templateService.findAll( + skip, + take, + search, + includeArchived, + ); + } + + @Get('availability') + @CanCreate() + @ApiOperation({ summary: 'Check if a template name is available' }) + @ApiOkResponse({ type: ActionTemplateAvailabilityDto }) + async checkTemplateNameAvailability( + @Query('name') name: string, + ): Promise { + const available = await this.templateService.isNameAvailable(name); + return { available }; + } + + @Get(':uuid/revisions') + @LoggedIn() + @ApiOperation({ summary: 'Get history/revisions of a template' }) + @ApiOkResponse({ type: ActionTemplatesDto }) + async findTemplateRevisions( + @ParameterUuid('uuid') uuid: string, + @QuerySkip('skip') skip: number, + @QueryTake('take') take: number, + ): Promise { + return this.templateService.findRevisions(uuid, skip, take); + } + + @Delete(':uuid') + @CanCreate() + @HttpCode(HttpStatus.NO_CONTENT) + @ApiOperation({ summary: 'Archive or delete a template' }) + @OutputDto(null) + async removeTemplate(@ParameterUuid('uuid') uuid: string): Promise { + await this.templateService.delete(uuid); + } +} diff --git a/backend/src/endpoints/templates/templates.module.ts b/backend/src/endpoints/templates/templates.module.ts new file mode 100644 index 000000000..05374b250 --- /dev/null +++ b/backend/src/endpoints/templates/templates.module.ts @@ -0,0 +1,22 @@ +import { TemplateService } from '@/services/template.service'; +import { ActionTemplateEntity } from '@kleinkram/backend-common/entities/action/action-template.entity'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TemplatesController } from './templates.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + ActionTemplateEntity, + ActionEntity, + UserEntity, + ]), + ], + providers: [TemplateService], + controllers: [TemplatesController], + exports: [TemplateService], +}) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class TemplatesModule {} diff --git a/backend/src/endpoints/topic/topic.controller.ts b/backend/src/endpoints/topic/topic.controller.ts index 7fd054c10..b05906dc2 100644 --- a/backend/src/endpoints/topic/topic.controller.ts +++ b/backend/src/endpoints/topic/topic.controller.ts @@ -1,8 +1,8 @@ -import { TopicNamesDto, TopicsDto } from '@common/api/types/topic.dto'; +import { ApiOkResponse } from '@/decorators'; +import { TopicService } from '@/services/topic.service'; +import { QuerySkip, QueryTake } from '@/validation/query-decorators'; +import { TopicNamesDto, TopicsDto } from '@kleinkram/api-dto'; import { Controller, Get } from '@nestjs/common'; -import { ApiOkResponse } from '../../decarators'; -import { TopicService } from '../../services/topic.service'; -import { QuerySkip, QueryTake } from '../../validation/query-decorators'; import { AddUser, AuthHeader } from '../auth/parameter-decorator'; import { LoggedIn } from '../auth/roles.decorator'; diff --git a/backend/src/endpoints/topic/topic.module.ts b/backend/src/endpoints/topic/topic.module.ts index c562b106a..475f30c7e 100644 --- a/backend/src/endpoints/topic/topic.module.ts +++ b/backend/src/endpoints/topic/topic.module.ts @@ -1,7 +1,7 @@ -import TopicEntity from '@common/entities/topic/topic.entity'; +import { TopicService } from '@/services/topic.service'; +import { TopicEntity } from '@kleinkram/backend-common/entities/topic/topic.entity'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { TopicService } from '../../services/topic.service'; import { TopicController } from './topic.controller'; @Module({ @@ -10,4 +10,5 @@ import { TopicController } from './topic.controller'; controllers: [TopicController], exports: [TopicService], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class TopicModule {} diff --git a/backend/src/endpoints/user/user.controller.ts b/backend/src/endpoints/user/user.controller.ts index 7c8d78f97..77ab1f715 100644 --- a/backend/src/endpoints/user/user.controller.ts +++ b/backend/src/endpoints/user/user.controller.ts @@ -1,19 +1,19 @@ -import { NoQueryParametersDto } from '@common/api/types/no-query-parameters.dto'; -import { PermissionsDto } from '@common/api/types/permissions.dto'; +import { ApiOkResponse, OutputDto } from '@/decorators'; +import { UserService } from '@/services/user.service'; +import { + QuerySkip, + QueryString, + QueryTake, +} from '@/validation/query-decorators'; import { CurrentAPIUserDto, + NoQueryParametersDto, + PermissionsDto, UserDto, UsersDto, -} from '@common/api/types/user.dto'; +} from '@kleinkram/api-dto'; import { Body, Controller, Get, Post, Query } from '@nestjs/common'; import { ApiOperation } from '@nestjs/swagger'; -import { ApiOkResponse, OutputDto } from '../../decarators'; -import { UserService } from '../../services/user.service'; -import { - QuerySkip, - QueryString, - QueryTake, -} from '../../validation/query-decorators'; import { AddUser, AuthHeader } from '../auth/parameter-decorator'; import { AdminOnly, LoggedIn, UserOnly } from '../auth/roles.decorator'; diff --git a/backend/src/endpoints/user/user.module.ts b/backend/src/endpoints/user/user.module.ts index b22f97456..b9c704bf0 100644 --- a/backend/src/endpoints/user/user.module.ts +++ b/backend/src/endpoints/user/user.module.ts @@ -1,10 +1,12 @@ -import AccountEntity from '@common/entities/auth/account.entity'; -import ApikeyEntity from '@common/entities/auth/apikey.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { ProjectAccessViewEntity } from '@common/viewEntities/project-access-view.entity'; +import { UserService } from '@/services/user.service'; +import { + AccountEntity, + ApiKeyEntity, + ProjectAccessViewEntity, + UserEntity, +} from '@kleinkram/backend-common'; import { Global, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserService } from '../../services/user.service'; import { UserController } from './user.controller'; @Global() @@ -12,7 +14,7 @@ import { UserController } from './user.controller'; imports: [ TypeOrmModule.forFeature([ UserEntity, - ApikeyEntity, + ApiKeyEntity, AccountEntity, ProjectAccessViewEntity, ]), @@ -23,9 +25,10 @@ import { UserController } from './user.controller'; UserService, TypeOrmModule.forFeature([ UserEntity, - ApikeyEntity, + ApiKeyEntity, ProjectAccessViewEntity, ]), ], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class UserModule {} diff --git a/backend/src/endpoints/worker/worker.controller.ts b/backend/src/endpoints/worker/worker.controller.ts index 57706b5d7..4705c4024 100644 --- a/backend/src/endpoints/worker/worker.controller.ts +++ b/backend/src/endpoints/worker/worker.controller.ts @@ -1,8 +1,8 @@ -import { ActionWorkersDto } from '@common/api/types/action-workers.dto'; +import { ApiOkResponse } from '@/decorators'; +import { WorkerService } from '@/services/worker.service'; +import { ActionWorkersDto } from '@kleinkram/api-dto'; import { Controller, Get } from '@nestjs/common'; import { ApiOperation } from '@nestjs/swagger'; -import { ApiOkResponse } from '../../decarators'; -import { WorkerService } from '../../services/worker.service'; import { LoggedIn } from '../auth/roles.decorator'; @Controller('worker') diff --git a/backend/src/endpoints/worker/worker.module.ts b/backend/src/endpoints/worker/worker.module.ts index 206f1d3d9..8a482632f 100644 --- a/backend/src/endpoints/worker/worker.module.ts +++ b/backend/src/endpoints/worker/worker.module.ts @@ -1,7 +1,7 @@ -import WorkerEntity from '@common/entities/worker/worker.entity'; +import { WorkerService } from '@/services/worker.service'; +import { WorkerEntity } from '@kleinkram/backend-common/entities/worker/worker.entity'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { WorkerService } from '../../services/worker.service'; import { WorkerController } from './worker.controller'; @Module({ @@ -10,4 +10,5 @@ import { WorkerController } from './worker.controller'; providers: [WorkerService], exports: [WorkerService], }) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class WorkerModule {} diff --git a/backend/src/logger.ts b/backend/src/logger.ts index 3698e50d3..72d6801fc 100644 --- a/backend/src/logger.ts +++ b/backend/src/logger.ts @@ -7,6 +7,7 @@ import { appVersion } from './app-version'; const messageOnly = winston.format.printf( ({ level, message }: TransformableInfo): string => { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions return `[${level.toUpperCase()}]: ${message}`; }, ); @@ -14,7 +15,7 @@ const messageOnly = winston.format.printf( const traceIdFormat = winston.format((info) => { const currentSpan = trace.getSpan(context.active()); if (currentSpan) { - info['trace_id'] = currentSpan.spanContext().traceId; + info.trace_id = currentSpan.spanContext().traceId; } return info; }); @@ -33,7 +34,9 @@ const logger = winston.createLogger({ interval: 5, labels: { job: 'backend', - container_id: process.env['HOSTNAME'], + + // eslint-disable-next-line @typescript-eslint/naming-convention + container_id: process.env.HOSTNAME, version: appVersion, }, json: true, @@ -45,8 +48,20 @@ const logger = winston.createLogger({ @Injectable() export class NestLoggerWrapper implements LoggerService { + // Messages to filter out from console logs (reduce initialization spam) + private shouldFilterMessage(message: string): boolean { + const filters = [ + 'dependencies initialized', + 'Mapped {/', // Route mapping logs + ]; + return filters.some((filter) => message.includes(filter)); + } + log(message: never, ...optionalParameters: never[]): void { - logger.info(message, ...optionalParameters); + // Filter out module initialization spam + if (!this.shouldFilterMessage(String(message))) { + logger.info(message, ...optionalParameters); + } } fatal(message: never, ...optionalParameters: never[]): void { diff --git a/backend/src/main.ts b/backend/src/main.ts index 0eb065a93..8024166ab 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,7 +1,8 @@ -import environment from '@common/environment'; +import environment from '@kleinkram/backend-common/environment'; import { NestFactory, Reflector } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import cookieParser from 'cookie-parser'; +import 'reflect-metadata'; import { AppModule } from './app.module'; import { AuthFlowExceptionRedirectFilter } from './routing/filters/auth-flow-exception'; import tracer from './tracing'; @@ -9,21 +10,38 @@ import tracer from './tracing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import * as fs from 'node:fs'; +import { register } from 'prom-client'; import logger, { NestLoggerWrapper } from './logger'; import { GlobalErrorFilter } from './routing/filters/global-error-filter'; import { GlobalResponseValidationInterceptor } from './routing/interceptors/output-validation'; import { AddVersionInterceptor } from './routing/interceptors/version-injector'; function saveEndpointsAsJson(app: INestApplication, filename: string): void { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const server = app.getHttpServer(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access const endpoints = server._events.request._router.stack + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access .filter((r: any) => r.route) + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any .map((r: any) => ({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access url: r.route.path, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access method: r.route.stack[0].method, })); - fs.writeFileSync(filename, JSON.stringify(endpoints, null, 2)); + try { + fs.writeFileSync(filename, JSON.stringify(endpoints, null, 2)); + } catch (error) { + logger.warn( + `Failed to save endpoints to ${filename}: ${String(error)}`, + ); + } } async function bootstrap(): Promise { @@ -99,8 +117,24 @@ async function bootstrap(): Promise { logger.debug('Save endpoints as JSON'); saveEndpointsAsJson(app, '.endpoints/__generated__endpoints.json'); logger.debug('Endpoints saved'); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (module.hot) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + module.hot.accept(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + module.hot.dispose(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + app.close(); + register.clear(); + }); + } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare const module: any; + // eslint-disable-next-line unicorn/prefer-top-level-await bootstrap().catch((error: unknown) => { logger.error('Failed to start application'); diff --git a/backend/src/routing/filters/auth-flow-exception.ts b/backend/src/routing/filters/auth-flow-exception.ts index da45d49e3..bb4cf3f3b 100644 --- a/backend/src/routing/filters/auth-flow-exception.ts +++ b/backend/src/routing/filters/auth-flow-exception.ts @@ -1,7 +1,7 @@ +import { AuthFlowException } from '@/types/auth-flow-exception'; import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; import { Response } from 'express'; import logger from '../../logger'; -import { AuthFlowException } from '../../types/auth-flow-exception'; @Catch(AuthFlowException) export class AuthFlowExceptionRedirectFilter implements ExceptionFilter { @@ -10,7 +10,7 @@ export class AuthFlowExceptionRedirectFilter implements ExceptionFilter { constructor() { logger.debug('AuthFlowExceptionRedirectFilter created'); - const frontendUrl = process.env['FRONTEND_URL']; + const frontendUrl = process.env.FRONTEND_URL; if (frontendUrl === undefined) { throw new Error('FRONTEND_URL env var not set'); } diff --git a/backend/src/routing/filters/global-error-filter.ts b/backend/src/routing/filters/global-error-filter.ts index ce24a2e10..18c1f402b 100644 --- a/backend/src/routing/filters/global-error-filter.ts +++ b/backend/src/routing/filters/global-error-filter.ts @@ -1,4 +1,6 @@ -import env from '@common/environment'; +import { appVersion } from '@/app-version'; +import { AuthFlowException } from '@/types/auth-flow-exception'; +import env from '@kleinkram/backend-common/environment'; import { ArgumentsHost, BadRequestException, @@ -9,10 +11,8 @@ import { } from '@nestjs/common'; import { HttpException } from '@nestjs/common/exceptions/http.exception'; import { Response } from 'express'; -import { appVersion } from 'src/app-version'; import { EntityNotFoundError } from 'typeorm'; import logger from '../../logger'; -import { AuthFlowException } from '../../types/auth-flow-exception'; /** * A global error filter that catches all errors and logs them. @@ -85,11 +85,16 @@ export class GlobalErrorFilter implements ExceptionFilter { } if (exception instanceof BadRequestException) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const resp: any = exception.getResponse(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access if (typeof resp === 'object' && resp.hasOwnProperty('message')) { response.status(400).json({ statusCode: 400, + ...resp, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access message: resp.message.toString(), }); return; diff --git a/backend/src/routing/interceptors/output-validation.ts b/backend/src/routing/interceptors/output-validation.ts index caa982b49..379175f85 100644 --- a/backend/src/routing/interceptors/output-validation.ts +++ b/backend/src/routing/interceptors/output-validation.ts @@ -14,13 +14,33 @@ import logger from '../../logger'; function validateResponseJSON(dto: ClassConstructor) { return (data: JSON): JSON => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (dto) { - const instance: T = plainToInstance(dto, data); - const errors = validateSync(instance, { - whitelist: true, - forbidNonWhitelisted: true, - forbidUnknownValues: true, - }); + const isArray = Array.isArray(dto); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const cls = isArray ? dto[0] : dto; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const instance: T = plainToInstance(cls, data); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let errors: any[] = []; + if (Array.isArray(instance)) { + for (const item of instance) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const itemErrors = validateSync(item, { + whitelist: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + }); + errors.push(...itemErrors); + } + } else { + errors = validateSync(instance as object, { + whitelist: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + }); + } if (errors.length > 0) { logger.error( @@ -33,6 +53,7 @@ function validateResponseJSON(dto: ClassConstructor) { logger.error(dto.name); logger.error( + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access `\n${errors.map((error) => error.toString()).join('\n')}`, ); throw new InternalServerErrorException( @@ -66,14 +87,18 @@ export class GlobalResponseValidationInterceptor implements NestInterceptor { // eslint-disable-next-line @typescript-eslint/no-explicit-any intercept(context: ExecutionContext, next: CallHandler): Observable { const target = context.getHandler(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const dto = this.reflector.get('outputDto', target); if (dto === null) return next.handle(); // explicitly disabled // enforce that a DTO must be defined uns if (dto === undefined) - throw new InternalServerErrorException('No DTO defined'); + throw new InternalServerErrorException( + `No output DTO defined for route ${target.name}.`, + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return next.handle().pipe(map(validateResponseJSON(dto))); } } diff --git a/backend/src/routing/interceptors/version-injector.ts b/backend/src/routing/interceptors/version-injector.ts index 65a9e8425..826aad1a5 100644 --- a/backend/src/routing/interceptors/version-injector.ts +++ b/backend/src/routing/interceptors/version-injector.ts @@ -1,3 +1,4 @@ +import { appVersion } from '@/app-version'; import { CallHandler, ExecutionContext, @@ -5,7 +6,6 @@ import { NestInterceptor, } from '@nestjs/common'; import { Observable } from 'rxjs'; -import { appVersion } from '../../app-version'; @Injectable() export class AddVersionInterceptor implements NestInterceptor { @@ -18,10 +18,15 @@ export class AddVersionInterceptor implements NestInterceptor { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any intercept(context: ExecutionContext, next: CallHandler): Observable { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const response = context.switchToHttp().getResponse(); // Set headers early, so they are included even if an error occurs + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access response.header('kleinkram-version', appVersion); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access response.header('Access-Control-Expose-Headers', 'kleinkram-version'); return next.handle(); diff --git a/backend/src/routing/middlewares/api-key-resolver-middleware.service.ts b/backend/src/routing/middlewares/api-key-resolver-middleware.service.ts index 499323af2..0f8fcfe2f 100644 --- a/backend/src/routing/middlewares/api-key-resolver-middleware.service.ts +++ b/backend/src/routing/middlewares/api-key-resolver-middleware.service.ts @@ -1,7 +1,7 @@ -import { CookieNames } from '@common/frontend_shared/enum'; +import { UserService } from '@/services/user.service'; +import { CookieNames } from '@kleinkram/shared'; import { Injectable, NestMiddleware } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; -import { UserService } from '../../services/user.service'; /** * @@ -17,6 +17,7 @@ export class APIKeyResolverMiddleware implements NestMiddleware { _: Response, next: NextFunction, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (request.cookies !== undefined) { const key = request.cookies[CookieNames.CLI_KEY] as | string @@ -27,6 +28,16 @@ export class APIKeyResolverMiddleware implements NestMiddleware { } } + if (!request.user) { + const headerKey = request.headers['x-api-key'] as + | string + | undefined; + if (headerKey) { + request.user = + await this.userService.findUserByAPIKey(headerKey); + } + } + // pass on to the next middleware function next(); } diff --git a/backend/src/routing/middlewares/audit-logger-middleware.service.ts b/backend/src/routing/middlewares/audit-logger-middleware.service.ts index 26631a59a..f63909f57 100644 --- a/backend/src/routing/middlewares/audit-logger-middleware.service.ts +++ b/backend/src/routing/middlewares/audit-logger-middleware.service.ts @@ -1,8 +1,8 @@ -import { CookieNames } from '@common/frontend_shared/enum'; +import { ActionService } from '@/services/action.service'; +import { CookieNames } from '@kleinkram/shared'; import { Injectable, NestMiddleware } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; import logger from '../../logger'; -import { ActionService } from '../../services/action.service'; /** * @@ -15,6 +15,7 @@ export class AuditLoggerMiddleware implements NestMiddleware { constructor(private actionService: ActionService) {} use(request: Request, _: Response, next: NextFunction): void { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!request.cookies) { next(); // pass on to the next middleware function return; diff --git a/backend/src/routing/middlewares/version-checker-middleware.service.ts b/backend/src/routing/middlewares/version-checker-middleware.service.ts index be14889a7..5d6e0e1ef 100644 --- a/backend/src/routing/middlewares/version-checker-middleware.service.ts +++ b/backend/src/routing/middlewares/version-checker-middleware.service.ts @@ -1,12 +1,27 @@ +import { appVersion } from '@/app-version'; import { Injectable, NestMiddleware } from '@nestjs/common'; +import { HttpException } from '@nestjs/common/exceptions/http.exception'; import { NextFunction, Request, Response } from 'express'; -import { appVersion } from '../../app-version'; import logger from '../../logger'; /** + * Configuration for forbidden version ranges. + * Supported patterns: + * - ` Minor -> Patch). + * + * @returns true if version1 is strictly less than version2 + * @example + * isLessThan('0.5.0', '0.6.0') // true + * isLessThan('1.0.1', '1.0.0') // false + */ private isLessThan(version1: string, version2: string): boolean { const v1Parts = version1.split('.').map(Number); const v2Parts = version2.split('.').map(Number); diff --git a/backend/src/serialization/action-template.ts b/backend/src/serialization/action-template.ts new file mode 100644 index 000000000..42f8910a5 --- /dev/null +++ b/backend/src/serialization/action-template.ts @@ -0,0 +1,33 @@ +import type { ActionTemplateDto } from '@kleinkram/api-dto'; +import type { ActionTemplateEntity } from '@kleinkram/backend-common/entities/action/action-template.entity'; +import { userEntityToDto } from './index'; + +export const actionTemplateEntityToDto = ( + actionTemplate: ActionTemplateEntity, + executionCount = 0, +): ActionTemplateDto => { + return { + uuid: actionTemplate.uuid, + description: actionTemplate.description, + archived: actionTemplate.isArchived, + accessRights: actionTemplate.accessRights, + command: actionTemplate.command ?? '', + cpuCores: actionTemplate.cpuCores, + cpuMemory: actionTemplate.cpuMemory, + entrypoint: actionTemplate.entrypoint ?? '', + gpuMemory: actionTemplate.gpuMemory, + imageName: actionTemplate.image_name, + maxRuntime: actionTemplate.maxRuntime, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + createdAt: actionTemplate.createdAt ?? new Date(), + name: actionTemplate.name, + version: actionTemplate.version.toString(), + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + creator: actionTemplate.creator + ? userEntityToDto(actionTemplate.creator) + : undefined, + + executionCount: executionCount, + }; +}; diff --git a/backend/src/serialization/action.ts b/backend/src/serialization/action.ts new file mode 100644 index 000000000..83a50e2b6 --- /dev/null +++ b/backend/src/serialization/action.ts @@ -0,0 +1,77 @@ +import { + ActionDto, + ActionWorkerDto, + AuditLogDto, + DockerImageDto, +} from '@kleinkram/api-dto'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; +import { WorkerEntity } from '@kleinkram/backend-common/entities/worker/worker.entity'; +import { actionTemplateEntityToDto } from './action-template'; +import { missionEntityToDto, userEntityToDto } from './index'; + +const workerEntityToDto = ( + worker: WorkerEntity | null | undefined, +): ActionWorkerDto | null => { + if (!worker) return null; + + return { + uuid: worker.uuid, + createdAt: worker.createdAt, + updatedAt: worker.updatedAt, + identifier: worker.identifier, + hostname: worker.hostname, + cpuMemory: worker.cpuMemory, + gpuModel: worker.gpuModel ?? null, + gpuMemory: worker.gpuMemory, + cpuCores: worker.cpuCores, + cpuModel: worker.cpuModel, + storage: worker.storage, + lastSeen: worker.lastSeen, + reachable: worker.reachable, + }; +}; + +export const actionEntityToDto = (action: ActionEntity): ActionDto => { + if (action.creator === undefined) { + throw new Error('Action must have a creator'); + } + + if (action.mission === undefined) { + throw new Error('Action must have a mission'); + } + + if (action.template === undefined) { + throw new Error('Action must have a template'); + } + + let runtime: number | undefined; + if (action.actionContainerStartedAt) { + const end = action.actionContainerExitedAt + ? action.actionContainerExitedAt.getTime() + : Date.now(); + runtime = (end - action.actionContainerStartedAt.getTime()) / 1000; + } + + return { + artifactUrl: action.artifact_path ?? '', + artifacts: action.artifacts, + artifactSize: action.artifact_size, + artifactFiles: action.artifact_files, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + auditLogs: (action.auditLogs as unknown as AuditLogDto[]) ?? [], + createdAt: action.createdAt, + creator: userEntityToDto(action.creator), + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + image: (action.image as DockerImageDto) ?? { repoDigests: [] }, + runtime, + + mission: missionEntityToDto(action.mission), + state: action.state, + stateCause: action.state_cause ?? '', + template: actionTemplateEntityToDto(action.template), + updatedAt: action.updatedAt, + uuid: action.uuid, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + worker: workerEntityToDto(action.worker)!, + }; +}; diff --git a/backend/src/serialization.ts b/backend/src/serialization/index.ts similarity index 66% rename from backend/src/serialization.ts rename to backend/src/serialization/index.ts index 04446e4b8..1e903c4dc 100644 --- a/backend/src/serialization.ts +++ b/backend/src/serialization/index.ts @@ -1,39 +1,64 @@ -import ActionTemplateEntity from '@common/entities/action/action-template.entity'; -import ActionEntity from '@common/entities/action/action.entity'; -import GroupMembershipEntity from '@common/entities/auth/group-membership.entity'; -import ProjectAccessEntity from '@common/entities/auth/project-access.entity'; -import FileEntity from '@common/entities/file/file.entity'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; -import TopicEntity from '@common/entities/topic/topic.entity'; -import UserEntity from '@common/entities/user/user.entity'; - -import { ActionWorkerDto } from '@common/api/types/action-workers.dto'; -import { ActionTemplateDto } from '@common/api/types/actions/action-template.dto'; -import { ActionDto } from '@common/api/types/actions/action.dto'; -import { AuditLogDto } from '@common/api/types/actions/audit-log.dto'; -import { DockerImageDto } from '@common/api/types/actions/docker-image.dto'; -import { LogsDto } from '@common/api/types/actions/logs.dto'; -import { FileDto, FileWithTopicDto } from '@common/api/types/file/file.dto'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { GroupMembershipEntity } from '@kleinkram/backend-common/entities/auth/group-membership.entity'; +import { ProjectAccessEntity } from '@kleinkram/backend-common/entities/auth/project-access.entity'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { TagTypeEntity } from '@kleinkram/backend-common/entities/tagType/tag-type.entity'; +import { TopicEntity } from '@kleinkram/backend-common/entities/topic/topic.entity'; + import { + AccessGroupDto, + CurrentAPIUserDto, + FileDto, + FileWithTopicDto, FlatMissionDto, + GroupMembershipDto, MinimumMissionDto, MissionDto, MissionWithCreatorDto, MissionWithFilesDto, -} from '@common/api/types/mission/mission.dto'; -import { ProjectDto } from '@common/api/types/project/base-project.dto'; -import { ProjectWithMissionsDto } from '@common/api/types/project/project-with-missions.dto'; -import { ProjectWithRequiredTagsDto } from '@common/api/types/project/project-with-required-tags.dto'; -import { TagDto, TagTypeDto } from '@common/api/types/tags/tags.dto'; -import { TopicDto } from '@common/api/types/topic.dto'; -import { GroupMembershipDto, UserDto } from '@common/api/types/user.dto'; + ProjectDto, + ProjectWithMissionsDto, + ProjectWithRequiredTagsDto, + TagDto, + TagTypeDto, + TopicDto, + UserDto, +} from '@kleinkram/api-dto'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; import { AccessGroupRights, AccessGroupType, -} from '@common/frontend_shared/enum'; + UserRole, +} from '@kleinkram/shared'; + +export const userEntityToDto = ( + user: UserEntity, + includeEmail = false, +): UserDto => { + return { + uuid: user.uuid, + name: user.name, + avatarUrl: user.avatarUrl ?? null, + email: includeEmail && user.email ? user.email : null, + }; +}; + +export const userEntityToCurrentAPIUserDto = ( + user: UserEntity, +): CurrentAPIUserDto => { + return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread + ...userEntityToDto(user, true), + role: user.role ?? UserRole.USER, + memberships: + user.memberships?.map((m) => + groupMembershipEntityToDto(m, false, user, true), + ) ?? [], + }; +}; export const missionEntityToDto = (mission: MissionEntity): MissionDto => { if (!mission.project) { @@ -41,10 +66,11 @@ export const missionEntityToDto = (mission: MissionEntity): MissionDto => { } return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread ...missionEntityToMinimumDto(mission), project: projectEntityToDto(mission.project), createdAt: mission.createdAt, - tags: mission.tags?.map(tagEntityToDto) || [], + tags: mission.tags?.map(tagEntityToDto) ?? [], updatedAt: mission.updatedAt, }; }; @@ -57,6 +83,7 @@ export const missionEntityToDtoWithCreator = ( } return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread ...missionEntityToDto(mission), creator: userEntityToDto(mission.creator), }; @@ -66,6 +93,7 @@ export const missionEntityToFlatDto = ( mission: MissionEntity, ): FlatMissionDto => { return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread ...missionEntityToDtoWithCreator(mission), filesCount: mission.fileCount ?? 0, size: mission.size ?? 0, @@ -84,6 +112,7 @@ export const missionEntityToDtoWithFiles = ( } return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread ...(missionEntityToDtoWithCreator(mission) as MissionWithFilesDto), files: mission.files.map((element) => fileEntityToDto(element)), tags: mission.tags.map((element) => tagEntityToDto(element)), @@ -99,56 +128,6 @@ export const missionEntityToMinimumDto = ( }; }; -export const actionTemplateEntityToDto = ( - actionTemplate: ActionTemplateEntity, -): ActionTemplateDto => { - return { - uuid: actionTemplate.uuid, - accessRights: actionTemplate.accessRights, - command: actionTemplate.command ?? '', - cpuCores: actionTemplate.cpuCores, - cpuMemory: actionTemplate.cpuMemory, - entrypoint: actionTemplate.entrypoint ?? '', - gpuMemory: actionTemplate.gpuMemory, - imageName: actionTemplate.image_name, - maxRuntime: actionTemplate.maxRuntime, - createdAt: actionTemplate.createdAt, - name: actionTemplate.name, - version: actionTemplate.version.toString(), - }; -}; - -export const actionEntityToDto = (action: ActionEntity): ActionDto => { - if (action.creator === undefined) { - throw new Error('Action must have a creator'); - } - - if (action.mission === undefined) { - throw new Error('Action must have a mission'); - } - - if (action.template === undefined) { - throw new Error('Action must have a template'); - } - - return { - artifactUrl: action.artifact_path ?? '', - artifacts: action.artifacts, - auditLogs: (action.auditLogs as unknown as AuditLogDto[]) ?? [], - createdAt: action.createdAt, - creator: userEntityToDto(action.creator), - image: (action.image as DockerImageDto) ?? { repoDigests: [] }, - logs: (action.logs as unknown as LogsDto[]) ?? [], - mission: missionEntityToDto(action.mission), - state: action.state, - stateCause: action.state_cause ?? '', - template: actionTemplateEntityToDto(action.template), - updatedAt: action.updatedAt, - uuid: action.uuid, - worker: action.worker as ActionWorkerDto, - }; -}; - export const tagTypeEntityToDto = (tagType: TagTypeEntity): TagTypeDto => { return { uuid: tagType.uuid, @@ -212,9 +191,11 @@ export const fileEntityToDtoWithTopic = ( topics = file.parent.topics; } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!topics) topics = []; return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread ...(fileEntityToDto(file) as FileWithTopicDto), topics: topics.map((element) => topicEntityToDto(element)), }; @@ -246,24 +227,31 @@ export const projectAccessEntityToDto = ( }; }; -export const groupMembershipEntityToDto = ( +export function groupMembershipEntityToDto( groupMembership: GroupMembershipEntity, includeEmail = false, -): GroupMembershipDto => { - if (groupMembership.user === undefined) { + userOverride?: UserEntity, + includeAccessGroup = false, +): GroupMembershipDto { + const user = groupMembership.user ?? userOverride; + + if (user === undefined) { throw new Error('Member can never be undefined'); } return { uuid: groupMembership.uuid, canEditGroup: groupMembership.canEditGroup, - accessGroup: null, // we don't want to return the access group + accessGroup: + includeAccessGroup && groupMembership.accessGroup + ? accessGroupEntityToDto(groupMembership.accessGroup) + : null, createdAt: groupMembership.createdAt, updatedAt: groupMembership.updatedAt, expirationDate: groupMembership.expirationDate ?? null, - user: userEntityToDto(groupMembership.user, includeEmail), + user: userEntityToDto(user, includeEmail), }; -}; +} export const projectEntityToDto = (project: ProjectEntity): ProjectDto => { return { @@ -285,6 +273,7 @@ export const projectEntityToDtoWithRequiredTags = ( } return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread ...(projectEntityToDto(project) as ProjectWithMissionsDto), creator: userEntityToDto(project.creator), missionCount: missionCount, @@ -302,9 +291,12 @@ export const projectEntityToDtoWithMissionCountAndTags = ( } return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread ...(projectEntityToDto(project) as ProjectWithRequiredTagsDto), creator: userEntityToDto(project.creator), missionCount: project.missionCount ?? 0, + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition requiredTags: project.requiredTags?.map(tagTypeEntityToDto) ?? [], }; }; @@ -343,14 +335,23 @@ export const tagEntityToDto = (tag: MetadataEntity): TagDto => { }; }; -export const userEntityToDto = ( - user: UserEntity, - includeEmail = false, -): UserDto => { +export function accessGroupEntityToDto( + accessGroup: AccessGroupEntity, +): AccessGroupDto { return { - uuid: user.uuid, - name: user.name, - avatarUrl: user.avatarUrl ?? null, - email: includeEmail && user.email ? user.email : null, + uuid: accessGroup.uuid, + name: accessGroup.name, + createdAt: accessGroup.createdAt, + updatedAt: accessGroup.updatedAt, + type: accessGroup.type, + hidden: accessGroup.hidden, + creator: accessGroup.creator + ? userEntityToDto(accessGroup.creator) + : null, + memberships: + accessGroup.memberships?.map((m) => + groupMembershipEntityToDto(m, false, undefined, false), + ) ?? [], + projectAccesses: [], }; -}; +} diff --git a/backend/src/services/access.service.ts b/backend/src/services/access.service.ts index d1ceac8be..3bef47489 100644 --- a/backend/src/services/access.service.ts +++ b/backend/src/services/access.service.ts @@ -1,21 +1,28 @@ -import { AccessGroupsDto } from '@common/api/types/access-control/access-groups.dto'; +import { AuthHeader } from '@/endpoints/auth/parameter-decorator'; import { + groupMembershipEntityToDto, + projectAccessEntityToDto, + userEntityToDto, +} from '@/serialization'; +import { + AccessGroupDto, + AccessGroupsDto, + GroupMembershipDto, ProjectAccessDto, ProjectAccessListDto, -} from '@common/api/types/access-control/project-access.dto'; -import { ProjectWithAccessRightsDto } from '@common/api/types/project/project-access.dto'; -import { ProjectWithMissionsDto } from '@common/api/types/project/project-with-missions.dto'; -import { AccessGroupDto, GroupMembershipDto } from '@common/api/types/user.dto'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import GroupMembershipEntity from '@common/entities/auth/group-membership.entity'; -import ProjectAccessEntity from '@common/entities/auth/project-access.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import UserEntity from '@common/entities/user/user.entity'; + ProjectWithAccessRightsDto, + ProjectWithMissionsDto, +} from '@kleinkram/api-dto'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { GroupMembershipEntity } from '@kleinkram/backend-common/entities/auth/group-membership.entity'; +import { ProjectAccessEntity } from '@kleinkram/backend-common/entities/auth/project-access.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; import { AccessGroupRights, AccessGroupType, UserRole, -} from '@common/frontend_shared/enum'; +} from '@kleinkram/shared'; import { ConflictException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { @@ -26,13 +33,7 @@ import { Not, Repository, } from 'typeorm'; -import { AuthHeader } from '../endpoints/auth/parameter-decorator'; import logger from '../logger'; -import { - groupMembershipEntityToDto, - projectAccessEntityToDto, - userEntityToDto, -} from '../serialization'; @Injectable() export class AccessService { @@ -165,6 +166,7 @@ export class AccessService { rights: rights, }) .andWhere('projectAccesses.useruuid = :user_uuid', { + // eslint-disable-next-line @typescript-eslint/naming-convention user_uuid: auth.user.uuid, }) .getExists(); @@ -232,6 +234,7 @@ export class AccessService { }); } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!personalAccessGroup) { throw new ConflictException('User has no personal access group'); } diff --git a/backend/src/services/action.service.ts b/backend/src/services/action.service.ts index 02a8f0084..41d41d46b 100644 --- a/backend/src/services/action.service.ts +++ b/backend/src/services/action.service.ts @@ -1,74 +1,53 @@ +import { addAccessConstraints } from '@/endpoints/auth/auth-helper'; +import { AuthHeader } from '@/endpoints/auth/parameter-decorator'; +import { actionEntityToDto } from '@/serialization/action'; import { - ActionTemplateDto, - ActionTemplatesDto, -} from '@common/api/types/actions/action-template.dto'; -import { ActionDto, ActionsDto } from '@common/api/types/actions/action.dto'; -import { - CreateTemplateDto, - UpdateTemplateDto, -} from '@common/api/types/create-template.dto'; -import { + ActionDto, + ActionLogsDto, + ActionQuery, + ActionsDto, ActionSubmitResponseDto, + PaginatedQueryDto, SubmitActionDto, -} from '@common/api/types/submit-action-response.dto'; -import { SubmitActionMulti } from '@common/api/types/submit-action.dto'; -import ActionTemplateEntity from '@common/entities/action/action-template.entity'; -import ActionEntity from '@common/entities/action/action.entity'; -import ApikeyEntity from '@common/entities/auth/apikey.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import environment from '@common/environment'; -import { - ActionState, - ArtifactState, - UserRole, -} from '@common/frontend_shared/enum'; -import { ActionDispatcherService } from '@common/modules/action-dispatcher/action-dispatcher.service'; -import { StorageService } from '@common/modules/storage/storage.service'; -import { ConflictException, Injectable } from '@nestjs/common'; + SubmitActionMulti, +} from '@kleinkram/api-dto'; +import { ApiKeyEntity } from '@kleinkram/backend-common'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import environment from '@kleinkram/backend-common/environment'; +import { ActionDispatcherService } from '@kleinkram/backend-common/modules/action-dispatcher/action-dispatcher.service'; +import { StorageService } from '@kleinkram/backend-common/modules/storage/storage.service'; +import { ArtifactState, UserRole } from '@kleinkram/shared'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Brackets, EntityManager, - FindOptionsWhere, - ILike, - QueryFailedError, Repository, SelectQueryBuilder, } from 'typeorm'; -import { addAccessConstraints } from '../endpoints/auth/auth-helper'; -import { AuthHeader } from '../endpoints/auth/parameter-decorator'; import logger from '../logger'; -import { actionEntityToDto, actionTemplateEntityToDto } from '../serialization'; @Injectable() export class ActionService { - private readonly DOCKER_NAMESPACE = - process.env['VITE_DOCKER_HUB_NAMESPACE']; - constructor( @InjectRepository(ActionEntity) private actionRepository: Repository, @InjectRepository(UserEntity) private userRepository: Repository, - @InjectRepository(ActionTemplateEntity) - private actionTemplateRepository: Repository, - @InjectRepository(ApikeyEntity) - private apikeyRepository: Repository, + @InjectRepository(ApiKeyEntity) + private apikeyRepository: Repository, @InjectRepository(MissionEntity) private missionRepository: Repository, private readonly actionDispatcher: ActionDispatcherService, private readonly storageService: StorageService, ) {} - /** - * Refactored submit: Delegates lifecycle management to the Dispatcher. - */ async submit( data: SubmitActionDto, auth: AuthHeader, ): Promise { - // Resolve Entities (Dispatcher requires Entities, not UUIDs) const creator = await this.userRepository.findOneOrFail({ where: { uuid: auth.user.uuid }, }); @@ -81,19 +60,12 @@ export class ActionService { data.templateUUID, mission, creator, - {}, // DTO doesn't currently support dynamic params, passing empty + {}, ); return { actionUUID }; } - async delete(actionUUID: string): Promise { - // Note: If the action is running, you might want to call actionDispatcher.stopAction(actionUUID) first. - // For now, maintaining existing behavior of entity deletion. - await this.actionRepository.delete(actionUUID); - return true; - } - async multiSubmit( data: SubmitActionMulti, user: AuthHeader, @@ -108,136 +80,61 @@ export class ActionService { ); } - async createTemplate( - data: CreateTemplateDto, - auth: AuthHeader, - ): Promise { - this.validateDockerNamespace(data.dockerImage); - - const template = this.actionTemplateRepository.create({ - ...data, - creator: { uuid: auth.user.uuid }, - image_name: data.dockerImage, - command: data.command ?? '', - entrypoint: data.entrypoint ?? '', - }); - - try { - const saved = await this.actionTemplateRepository.save(template); - return actionTemplateEntityToDto(saved); - } catch (error) { - // Postgres Error 23505 is Unique Violation - if ( - error instanceof QueryFailedError && - error.driverError?.code === '23505' - ) { - throw new ConflictException( - 'Template with this name already exists', - ); - } - throw error; - } - } - - async createNewVersion( - data: UpdateTemplateDto, - auth: AuthHeader, - ): Promise { - const currentTemplate = - await this.actionTemplateRepository.findOneOrFail({ - where: { uuid: data.uuid }, - }); - - if (this.isMetadataUpdateOnly(currentTemplate, data)) { - currentTemplate.searchable = true; - return actionTemplateEntityToDto( - await this.actionTemplateRepository.save(currentTemplate), - ); - } - - const creator = await this.userRepository.findOneOrFail({ - where: { uuid: auth.user.uuid }, - }); - const nextVersion = await this.calculateNextVersion( - data.name, - data.searchable, - ); - - const newTemplate = this.actionTemplateRepository.create({ - ...data, - image_name: data.dockerImage, - creator, - version: nextVersion, - command: data.command ?? '', - entrypoint: data.entrypoint ?? '', - }); - - return actionTemplateEntityToDto( - await this.actionTemplateRepository.save(newTemplate), - ); - } - - async listTemplates( - skip: number, - take: number, - search: string, - ): Promise { - const where: FindOptionsWhere = { - searchable: true, - }; - if (search !== '') { - where.name = ILike(`%${search}%`); - } - const [templates, count] = - await this.actionTemplateRepository.findAndCount({ - where, - skip, - take, - relations: ['creator'], - }); - - return { - count, - data: templates.map((element) => - actionTemplateEntityToDto(element), - ), + async findAll(query: ActionQuery, auth: AuthHeader): Promise { + const { skip, take, - }; - } - - async listActions( - projectUuid: string | undefined, - missionUuid: string | undefined, - userUUID: string, - skip: number, - take: number, - sortBy: string, - sortDirection: 'ASC' | 'DESC', - search: string, - ): Promise { + sortBy, + sortDirection, + search, + projectUuid, + missionUuid, + states, + } = query; const user = await this.userRepository.findOneOrFail({ - where: { uuid: userUUID }, + where: { uuid: auth.user.uuid }, }); const isAdmin = user.role === UserRole.ADMIN; const qb = this.buildBaseActionQuery(); - // Apply Filters + // Standard Filters if (projectUuid) qb.andWhere('project.uuid = :projectUuid', { projectUuid }); if (missionUuid) qb.andWhere('mission.uuid = :missionUuid', { missionUuid }); if (search) this.applySearchFilter(qb, search); - // Apply Sorting - if (sortBy && ['ASC', 'DESC'].includes(sortDirection)) { - qb.orderBy(`action.${sortBy}`, sortDirection); + // State Filter (Replaces /running endpoint) + if (states) { + qb.andWhere('action.state IN (:...states)', { states }); + } + + if (query.templateName) { + qb.andWhere('template.name ILIKE :templateName', { + templateName: `%${query.templateName}%`, + }); + } + + // Sorting + if ( + sortBy && + sortDirection && + ['ASC', 'DESC'].includes(sortDirection) + ) { + if (sortBy.includes('.')) { + qb.orderBy(sortBy, sortDirection as 'ASC' | 'DESC'); + } else { + qb.orderBy(`action.${sortBy}`, sortDirection as 'ASC' | 'DESC'); + } + } else { + // Default sort: newest first + qb.orderBy('action.createdAt', 'DESC'); } - // Apply Security & Pagination + // Security if (!isAdmin) { - addAccessConstraints(qb, userUUID); + addAccessConstraints(qb, auth.user.uuid); } const [actions, count] = await qb @@ -247,8 +144,8 @@ export class ActionService { return { count, - skip, - take, + skip: skip ?? 0, + take: take ?? count, data: actions.map((element) => actionEntityToDto(element)), }; } @@ -261,6 +158,7 @@ export class ActionService { 'mission.project', 'creator', 'template', + 'template.creator', 'worker', ], }); @@ -277,47 +175,40 @@ export class ActionService { return dto; } - async runningActions( - userUUID: string, - skip: number, - take: number, - ): Promise { - const user = await this.userRepository.findOneOrFail({ - where: { uuid: userUUID }, + async getLogs( + actionUuid: string, + query: PaginatedQueryDto, + ): Promise { + const action = await this.actionRepository.findOneOrFail({ + where: { uuid: actionUuid }, + select: ['uuid', 'logs'], }); - const qb = this.buildBaseActionQuery(); - qb.andWhere('action.state IN (:...states)', { - states: [ActionState.PENDING, ActionState.PROCESSING], - }); + const logs = action.logs ?? []; + const count = logs.length; + const data = logs.slice(query.skip, query.skip + query.take); - if (user.role !== UserRole.ADMIN) { - addAccessConstraints(qb, userUUID); - } - - const [actions, count] = await qb - .skip(skip) - .take(take) - .getManyAndCount(); return { count, - data: actions.map((element) => actionEntityToDto(element)), - skip, - take, + skip: query.skip, + take: query.take, + data, }; } + async delete(actionUUID: string): Promise { + await this.actionRepository.delete(actionUUID); + return true; + } + async writeAuditLog( apiKey: string, - auditLog: { - method: string; - url: string; - }, + auditLog: { method: string; url: string }, ): Promise { await this.apikeyRepository.manager.transaction( async (manager: EntityManager): Promise => { - const key: ApikeyEntity = await manager.findOneOrFail( - ApikeyEntity, + const key: ApiKeyEntity = await manager.findOneOrFail( + ApiKeyEntity, { where: { apikey: apiKey }, relations: ['action'], @@ -334,60 +225,13 @@ export class ActionService { ); } - private validateDockerNamespace(imageName: string): void { - if ( - this.DOCKER_NAMESPACE && - !imageName.startsWith(this.DOCKER_NAMESPACE) - ) { - throw new ConflictException( - `Only images from the ${this.DOCKER_NAMESPACE} namespace are allowed`, - ); - } - } - - private async calculateNextVersion( - name: string, - isSearchable: boolean, - ): Promise { - const direction = isSearchable ? 'DESC' : 'ASC'; - const lastVersionTemplate = await this.actionTemplateRepository.findOne( - { - where: { name }, - order: { version: direction }, - }, - ); - - const currentVersion = lastVersionTemplate?.version ?? 0; - const change = isSearchable ? 1 : -1; - - return currentVersion + change; - } - - private isMetadataUpdateOnly( - current: ActionTemplateEntity, - incoming: UpdateTemplateDto, - ): boolean { - if (current.searchable) return false; - if (!incoming.searchable) return false; - - return ( - current.image_name === incoming.dockerImage && - current.command === incoming.command && - current.cpuCores === incoming.cpuCores && - current.cpuMemory === incoming.cpuMemory && - current.gpuMemory === incoming.gpuMemory && - current.maxRuntime === incoming.maxRuntime && - current.entrypoint === incoming.entrypoint && - current.accessRights === incoming.accessRights - ); - } - private buildBaseActionQuery(): SelectQueryBuilder { return this.actionRepository .createQueryBuilder('action') .leftJoinAndSelect('action.mission', 'mission') .leftJoinAndSelect('mission.project', 'project') .leftJoinAndSelect('action.template', 'template') + .leftJoinAndSelect('template.creator', 'templateCreator') .leftJoinAndSelect('action.creator', 'creator'); } @@ -428,6 +272,7 @@ export class ActionService { `${action.uuid}.tar.gz`, 4 * 60 * 60, { + // eslint-disable-next-line @typescript-eslint/naming-convention 'response-content-disposition': `attachment; filename="${friendlyFilename}"`, }, ); diff --git a/backend/src/services/auth.service.ts b/backend/src/services/auth.service.ts index b63080916..b75ff0d90 100644 --- a/backend/src/services/auth.service.ts +++ b/backend/src/services/auth.service.ts @@ -1,13 +1,13 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import GroupMembershipEntity from '@common/entities/auth/group-membership.entity'; -import UserEntity from '@common/entities/user/user.entity'; +import { AuthFlowException } from '@/types/auth-flow-exception'; +import { AffiliationGroupService } from '@kleinkram/backend-common'; +import { AccountEntity } from '@kleinkram/backend-common/entities/auth/account.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; import { - AccessGroupType, + AccessGroupConfig, CookieNames, Providers, UserRole, -} from '@common/frontend_shared/enum'; +} from '@kleinkram/shared'; import { Injectable, OnModuleInit } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; @@ -15,8 +15,6 @@ import { InjectRepository } from '@nestjs/typeorm'; import { JwtPayload } from 'jsonwebtoken'; import { Repository } from 'typeorm'; import logger from '../logger'; -import { AccessGroupConfig } from '../types/access-group-config'; -import { AuthFlowException } from '../types/auth-flow-exception'; @Injectable() export class AuthService implements OnModuleInit { @@ -28,26 +26,30 @@ export class AuthService implements OnModuleInit { private accountRepository: Repository, @InjectRepository(UserEntity) private userRepository: Repository, - @InjectRepository(AccessGroupEntity) - private accessGroupRepository: Repository, - @InjectRepository(GroupMembershipEntity) - private groupMembershipRepository: Repository, + private affiliationGroupService: AffiliationGroupService, private configService: ConfigService, ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const config = this.configService.get('accessConfig'); if (config === undefined) throw new Error('Access config not found'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.config = config; } async onModuleInit(): Promise { - await createAccessGroups(this.accessGroupRepository, this.config); + await this.affiliationGroupService.createAccessGroups(this.config); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any async validateAndCreateUserByGitHub(profile: any): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { id, emails, displayName, photos } = profile; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const email = emails[0].value; const account = await this.accountRepository.findOne({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment where: { oauthID: id, provider: Providers.GITHUB }, relations: ['user'], }); @@ -64,18 +66,26 @@ export class AuthService implements OnModuleInit { } return this.create( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument id, Providers.GITHUB, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument email, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument displayName, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access photos[0].value, ); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any async validateAndCreateUserByFakeOAuth(profile: any): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { id, email, displayName, photo } = profile; const account = await this.accountRepository.findOne({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment where: { oauthID: id, provider: Providers.FakeOAuth }, relations: ['user'], }); @@ -91,14 +101,20 @@ export class AuthService implements OnModuleInit { return account.user; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return this.create(id, Providers.FakeOAuth, email, displayName, photo); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any async validateAndCreateUserByGoogle(profile: any): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { id, emails, displayName, photos } = profile; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const email = emails[0].value; const account = await this.accountRepository.findOne({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment where: { oauthID: id, provider: Providers.GOOGLE }, relations: ['user'], }); @@ -115,10 +131,15 @@ export class AuthService implements OnModuleInit { } return this.create( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument id, Providers.GOOGLE, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument email, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument displayName, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access photos[0].value, ); } @@ -155,13 +176,12 @@ export class AuthService implements OnModuleInit { email: string, username: string, picture: string, - ) { + ): Promise { return createNewUser( this.config, this.userRepository, this.accountRepository, - this.accessGroupRepository, - this.groupMembershipRepository, + this.affiliationGroupService, { oauthID, provider, @@ -176,115 +196,17 @@ export class AuthService implements OnModuleInit { /** * Create access groups from the access group config. * - * @param accessGroupRepository - * @param config - */ -export const createAccessGroups = async ( - accessGroupRepository: Repository, - config: AccessGroupConfig, -) => { - // Read access_config/*.json and create access groups - await Promise.all( - config.access_groups.map(async (group) => { - const databaseGroup = await accessGroupRepository.findOne({ - where: { uuid: group.uuid }, - }); - if (!databaseGroup) { - const newGroup = accessGroupRepository.create({ - name: group.name, - uuid: group.uuid, - type: AccessGroupType.AFFILIATION, - creator: {}, - }); - return accessGroupRepository.save(newGroup); - } - }), - ); -}; - -/** - * Create a primary access group for the user. - * - * @param user - * @param accessGroupRepository - */ -async function createPrimaryGroup( - user: UserEntity, - accessGroupRepository: Repository, -) { - logger.debug(`Add user ${user.uuid} to primary access group`); - - let primaryGroupName = user.name; - - const exists = await accessGroupRepository.exists({ - where: { name: primaryGroupName }, - }); - - if (exists) { - const randomSuffix = Math.random().toString(36).slice(7); - primaryGroupName = `${user.name} ${randomSuffix}`; - logger.debug( - `Primary group name already exists, using ${primaryGroupName}`, - ); - } - - const primaryGroup = accessGroupRepository.create({ - name: primaryGroupName, - type: AccessGroupType.PRIMARY, - hidden: false, - memberships: [ - { - canEditGroup: false, - user: { uuid: user.uuid }, - }, - ], - }); - await accessGroupRepository.save(primaryGroup); -} - -/** - * Add user to affiliation groups based on their email address. - * * @param config - * @param user - * @param accessGroupRepository - * @param groupMembershipRepository + * @param userRepository + * @param accountRepository + * @param affiliationGroupService + * @param options */ -async function addToAffiliationGroups( - config: AccessGroupConfig, - user: UserEntity, - accessGroupRepository: Repository, - groupMembershipRepository: Repository, -) { - await Promise.all( - config.emails.map((_config) => { - if (user.email?.endsWith(_config.email)) { - return Promise.all( - _config.access_groups.map(async (uuid) => { - const group = await accessGroupRepository.findOneOrFail( - { - where: { uuid }, - }, - ); - const affiliationGroup = - groupMembershipRepository.create({ - user: { uuid: user.uuid }, - accessGroup: { uuid: group.uuid }, - }); - return groupMembershipRepository.save(affiliationGroup); - }), - ); - } - }), - ); -} - export const createNewUser = async ( config: AccessGroupConfig, userRepository: Repository, accountRepository: Repository, - accessGroupRepository: Repository, - groupMembershipRepository: Repository, + affiliationGroupService: AffiliationGroupService, options: { oauthID: string; provider: Providers; @@ -344,14 +266,9 @@ export const createNewUser = async ( // Create and Link Access Groups ///////////////////////////////////////////////////////// - await createPrimaryGroup(user, accessGroupRepository); + await affiliationGroupService.createPrimaryGroup(user); - await addToAffiliationGroups( - config, - user, - accessGroupRepository, - groupMembershipRepository, - ); + await affiliationGroupService.addToAffiliationGroups(config, user); return await userRepository.findOneOrFail({ where: { uuid: user.uuid }, diff --git a/backend/src/services/category.service.ts b/backend/src/services/category.service.ts index d39e4b60f..acfac75be 100644 --- a/backend/src/services/category.service.ts +++ b/backend/src/services/category.service.ts @@ -1,10 +1,10 @@ -import { CategoriesDto } from '@common/api/types/category.dto'; -import CategoryEntity from '@common/entities/category/category.entity'; -import FileEntity from '@common/entities/file/file.entity'; +import { AuthHeader } from '@/endpoints/auth/parameter-decorator'; +import { CategoriesDto } from '@kleinkram/api-dto'; +import { CategoryEntity } from '@kleinkram/backend-common/entities/category/category.entity'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FindOptionsWhere, ILike, Repository } from 'typeorm'; -import { AuthHeader } from '../endpoints/auth/parameter-decorator'; import logger from '../logger'; @Injectable() diff --git a/backend/src/services/dbdumper.service.ts b/backend/src/services/dbdumper.service.ts index 8e9204139..d596c46cc 100644 --- a/backend/src/services/dbdumper.service.ts +++ b/backend/src/services/dbdumper.service.ts @@ -1,5 +1,5 @@ -import env from '@common/environment'; -import { StorageService } from '@common/modules/storage/storage.service'; +import env from '@kleinkram/backend-common/environment'; +import { StorageService } from '@kleinkram/backend-common/modules/storage/storage.service'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Cron, CronExpression } from '@nestjs/schedule'; @@ -50,12 +50,14 @@ export class DBDumper { await unlinkAsync(dumpFile); return dumpFile; + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { // Attempt to clean up if the file was created but upload failed if (fs.existsSync(dumpFile)) { await unlinkAsync(dumpFile).catch(() => ({})); } + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access throw new Error(`Failed to create database dump: ${error.message}`); } } diff --git a/backend/src/services/file-guard.service.ts b/backend/src/services/file-guard.service.ts index 1da97ee0b..583519c78 100644 --- a/backend/src/services/file-guard.service.ts +++ b/backend/src/services/file-guard.service.ts @@ -1,11 +1,11 @@ -import ApikeyEntity from '@common/entities/auth/apikey.entity'; -import FileEntity from '@common/entities/file/file.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { AccessGroupRights, UserRole } from '@common/frontend_shared/enum'; +import { MissionGuardService } from '@/endpoints/auth/mission-guard.service'; +import { ApiKeyEntity } from '@kleinkram/backend-common'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { AccessGroupRights, UserRole } from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { MissionGuardService } from '../endpoints/auth/mission-guard.service'; import logger from '../logger'; import { ProjectGuardService } from './project-guard.service'; @@ -23,6 +23,7 @@ export class FileGuardService { fileUUID: string, rights: AccessGroupRights = AccessGroupRights.READ, ) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!fileUUID || !user) { logger.error( `FileGuard: File UUID (${fileUUID}) or User (${user.uuid}) not provided. Requesting ${rights.toString()} access.`, @@ -68,6 +69,7 @@ export class FileGuardService { filename: string, rights: AccessGroupRights = AccessGroupRights.READ, ) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!filename || !user) { logger.error( `FileGuard: Filename (${filename}) or User (${user.uuid}) not provided. Requesting ${rights.toString()} access.`, @@ -81,7 +83,7 @@ export class FileGuardService { } async canKeyAccessFileByName( - apikey: ApikeyEntity, + apikey: ApiKeyEntity, filename: string, rights: AccessGroupRights = AccessGroupRights.READ, ) { @@ -98,7 +100,7 @@ export class FileGuardService { } async canKeyAccessFile( - apiKey: ApikeyEntity, + apiKey: ApiKeyEntity, fileUUID: string, rights: AccessGroupRights = AccessGroupRights.READ, ) { @@ -120,6 +122,7 @@ export class FileGuardService { fileUUIDs: string[], rights: AccessGroupRights = AccessGroupRights.READ, ) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!fileUUIDs || !user) { logger.error( `FileGuard: File UUIDs (${fileUUIDs.toString()}) or User (${user.uuid}) not provided. Requesting ${rights.toString()} access.`, diff --git a/backend/src/services/file.service.ts b/backend/src/services/file.service.ts index e170a2250..8556af309 100644 --- a/backend/src/services/file.service.ts +++ b/backend/src/services/file.service.ts @@ -1,9 +1,10 @@ -import { SortOrder } from '@common/api/types/pagination'; -import { UpdateFile } from '@common/api/types/update-file.dto'; -import FileEntity from '@common/entities/file/file.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import env from '@common/environment'; +import { SortOrder, UpdateFile } from '@kleinkram/api-dto'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { IngestionJobEntity } from '@kleinkram/backend-common/entities/file/ingestion-job.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import env from '@kleinkram/backend-common/environment'; import { DataType, FileEventType, @@ -12,7 +13,7 @@ import { FileType, HealthStatus, UserRole, -} from '@common/frontend_shared/enum'; +} from '@kleinkram/shared'; import { BadRequestException, Injectable, @@ -26,6 +27,7 @@ import { DataSource, In, MoreThan, + QueryFailedError, Repository, SelectQueryBuilder, } from 'typeorm'; @@ -35,31 +37,35 @@ import { addMissionFilters, addProjectFilters, addSort, + convertGlobToLikePattern, } from './utilities'; import { + FileEventsDto, FileExistsResponseDto, - TemporaryFileAccessDto, + FilesDto, + FileWithTopicDto, + StorageOverviewDto, TemporaryFileAccessesDto, -} from '@common/api/types/file/access.dto'; -import { FileEventsDto } from '@common/api/types/file/file-event.dto'; -import { FileWithTopicDto } from '@common/api/types/file/file.dto'; -import { FilesDto } from '@common/api/types/file/files.dto'; -import { StorageOverviewDto } from '@common/api/types/storage-overview.dto'; -import { FileAuditService } from '@common/audit/file-audit.service'; -import { redis } from '@common/consts'; -import CategoryEntity from '@common/entities/category/category.entity'; -import FileEventEntity from '@common/entities/file/file-event.entity'; -import IngestionJobEntity from '@common/entities/file/ingestion-job.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { StorageService } from '@common/modules/storage/storage.service'; +} from '@kleinkram/api-dto'; +import { FileAuditService } from '@kleinkram/backend-common/audit/file-audit.service'; +import { redis } from '@kleinkram/backend-common/consts'; +import { CategoryEntity } from '@kleinkram/backend-common/entities/category/category.entity'; +import { FileEventEntity } from '@kleinkram/backend-common/entities/file/file-event.entity'; + +import { TagTypeEntity } from '@kleinkram/backend-common/entities/tagType/tag-type.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { StorageService } from '@kleinkram/backend-common/modules/storage/storage.service'; import Queue from 'bull'; +// @ts-ignore import Credentials from 'minio/dist/main/Credentials'; +// @ts-ignore import { BucketItem } from 'minio/dist/main/internal/type'; import { addAccessConstraints, addAccessConstraintsToFileQuery, + addAccessConstraintsToMissionQuery, + addAccessConstraintsToProjectQuery, } from '../endpoints/auth/auth-helper'; import logger from '../logger'; @@ -69,6 +75,24 @@ const FIND_MANY_SORT_KEYS = { createdAt: 'file.createdAt', updatedAt: 'file.updatedAt', creator: 'user.name', + size: 'file.size', + state: 'file.state', + date: 'file.date', + + // eslint-disable-next-line @typescript-eslint/naming-convention + 'file.filename': 'file.filename', + + // eslint-disable-next-line @typescript-eslint/naming-convention + 'file.createdAt': 'file.createdAt', + + // eslint-disable-next-line @typescript-eslint/naming-convention + 'file.updatedAt': 'file.updatedAt', + // eslint-disable-next-line @typescript-eslint/naming-convention + 'file.size': 'file.size', + // eslint-disable-next-line @typescript-eslint/naming-convention + 'file.state': 'file.state', + // eslint-disable-next-line @typescript-eslint/naming-convention + 'file.date': 'file.date', }; @Injectable() @@ -164,6 +188,171 @@ export class FileService implements OnModuleInit { }; } + /** + * Checks if the user has access to the specified missions and projects. + * + * This method is NOT intended for fine-grained access control, but rather + * to produce nice error messages when a user tries to access resources they + * should not. + */ + async checkResourceAccess( + projectUuids: string[], + missionUuids: string[], + userUuid: string, + ): Promise { + // Verify Projects + if (projectUuids.length > 0) { + const uniqueProjectUuids = [...new Set(projectUuids)]; + + let query = this.projectRepository.createQueryBuilder('project'); + + // Filter by the specific requested IDs + query.where('project.uuid IN (:...uuids)', { + uuids: uniqueProjectUuids, + }); + + // Apply standard security constraints (Admins see all; Users see their own) + query = addAccessConstraintsToProjectQuery(query, userUuid); + + // Fetch allowed IDs + const foundProjects = await query.select('project.uuid').getMany(); + const foundUuids = new Set(foundProjects.map((p) => p.uuid)); + + // Calculate missing + const missing = uniqueProjectUuids.filter( + (id) => !foundUuids.has(id), + ); + + if (missing.length > 0) { + throw new NotFoundException( + `The following Project UUIDs do not exist or you do not have access: ${missing.join(', ')}`, + ); + } + } + + // Verify Missions + if (missionUuids.length > 0) { + const uniqueMissionUuids = [...new Set(missionUuids)]; + + let query = this.missionRepository + .createQueryBuilder('mission') + .select('mission.uuid') + .leftJoin('mission.project', 'project'); + + // Filter by the specific requested IDs + query.where('mission.uuid IN (:...uuids)', { + uuids: uniqueMissionUuids, + }); + + // Apply standard security constraints + query = addAccessConstraintsToMissionQuery(query, userUuid); + + // Fetch allowed IDs + const foundMissions = await query.select('mission.uuid').getMany(); + const foundUuids = new Set(foundMissions.map((m) => m.uuid)); + + // Calculate missing + const missing = uniqueMissionUuids.filter( + (id) => !foundUuids.has(id), + ); + + if (missing.length > 0) { + throw new NotFoundException( + `The following Mission UUIDs do not exist or you do not have access: ${missing.join(', ')}`, + ); + } + } + } + + /** + * + * Checks if the user has access to the specified missions and projects by name patterns. + * This method supports exact matches as well as wildcard patterns. + * + * This method is NOT intended for fine-grained access control, but rather + * to produce nice error messages when a user tries to access resources they + * should not. + * + */ + async checkResourceAccessByName( + projectNamePatterns: string[], + missionNamePatterns: string[], + userUuid: string, + exactMatch = false, + ): Promise { + logger.debug( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Checking resource access by name for user ${userUuid}. Projects: ${projectNamePatterns}, Missions: ${missionNamePatterns}, Exact: ${exactMatch}`, + ); + + // We use addProjectFilters which supports exactMatch natively + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (projectNamePatterns && projectNamePatterns.length > 0) { + const missingPatterns: string[] = []; + + for (const pattern of projectNamePatterns) { + let query = + this.projectRepository.createQueryBuilder('project'); + + query = addAccessConstraintsToProjectQuery(query, userUuid); + query = addProjectFilters( + query, + this.projectRepository, + [], + [pattern], + exactMatch, + ); + + const count = await query.getCount(); + if (count === 0) { + missingPatterns.push(pattern); + } + } + + if (missingPatterns.length > 0) { + throw new NotFoundException( + `The following Project patterns matched no accessible resources: ${missingPatterns.join(', ')}`, + ); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (missionNamePatterns && missionNamePatterns.length > 0) { + const missingPatterns: string[] = []; + + for (const pattern of missionNamePatterns) { + let query = this.missionRepository + .createQueryBuilder('mission') + .leftJoin('mission.project', 'project'); + + query = addAccessConstraintsToMissionQuery(query, userUuid); + + if (exactMatch) { + query.andWhere('LOWER(mission.name) = LOWER(:pattern)', { + pattern, + }); + } else { + const likePattern = convertGlobToLikePattern(pattern); + // Use standard wildcard match (case-insensitive) + query.andWhere('LOWER(mission.name) LIKE :pattern', { + pattern: `%${likePattern.toLowerCase()}%`, + }); + } + + const count = await query.getCount(); + if (count === 0) { + missingPatterns.push(pattern); + } + } + + if (missingPatterns.length > 0) { + throw new NotFoundException( + `The following Mission patterns matched no accessible resources: ${missingPatterns.join(', ')}`, + ); + } + } + } + /** * Finds and paginates files based on a comprehensive set of filters. * @@ -185,6 +374,7 @@ export class FileService implements OnModuleInit { categories: string, matchAllTopics: boolean, fileTypes: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any tags: Record, userUUID: string, take: number, @@ -207,6 +397,7 @@ export class FileService implements OnModuleInit { // ADMIN users see all, others are constrained if (user.role !== UserRole.ADMIN) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment idQuery = addAccessConstraints(idQuery, userUUID); } @@ -242,6 +433,7 @@ export class FileService implements OnModuleInit { this._applyFileTypeFilter(idQuery, fileTypes); this._applyTopicFilter(idQuery, topics, matchAllTopics); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (health) { logger.debug(`Filtering files by health: ${health}`); switch (health) { @@ -282,6 +474,7 @@ export class FileService implements OnModuleInit { } // The tag filter is async, so it must be awaited + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (tags && Object.keys(tags).length > 0) { await this._applyTagFilter(idQuery, tags); } @@ -290,7 +483,10 @@ export class FileService implements OnModuleInit { // and allow 'HAVING' clauses for topics and tags idQuery.groupBy('file.uuid'); - idQuery.orderBy(sort, sortOrder).offset(skip).limit(take); + const order = sortOrder === 'ASC' ? SortOrder.ASC : SortOrder.DESC; + + idQuery = addSort(idQuery, FIND_MANY_SORT_KEYS, sort, order); + idQuery.offset(skip).limit(take); const [fileIdObjects, count] = await idQuery.getManyAndCount(); @@ -307,16 +503,18 @@ export class FileService implements OnModuleInit { const fileIds = fileIdObjects.map((file) => file.uuid); // It must re-apply joins (for selection) and sorting. - const files = await this.fileRepository + let filesQuery = this.fileRepository .createQueryBuilder('file') .leftJoinAndSelect('file.mission', 'mission') .leftJoinAndSelect('mission.project', 'project') .leftJoinAndSelect('file.topics', 'topic') .leftJoinAndSelect('file.creator', 'creator') .leftJoinAndSelect('file.categories', 'category') - .where('file.uuid IN (:...fileIds)', { fileIds }) - .orderBy(sort, sortOrder) - .getMany(); + .where('file.uuid IN (:...fileIds)', { fileIds }); + + filesQuery = addSort(filesQuery, FIND_MANY_SORT_KEYS, sort, order); + + const files = await filesQuery.getMany(); return { count, @@ -352,6 +550,8 @@ export class FileService implements OnModuleInit { )) { validTypesLookup.set(type.toLowerCase(), type); } + // Manually add 'yml' to map to YAML since we merged them + validTypesLookup.set('yml', FileType.YAML); // Map requested types to their valid, cased enum values and deduplicate const typesToFilter = [ @@ -374,6 +574,7 @@ export class FileService implements OnModuleInit { } else { // No valid types were provided (e.g., "garbage,foo") logger.warn(`No valid file types found in filter: ${fileTypes}`); + query.andWhere('1 = 0'); // Force query to return no results } } @@ -421,6 +622,7 @@ export class FileService implements OnModuleInit { */ private async _applyTagFilter( query: SelectQueryBuilder, + // eslint-disable-next-line @typescript-eslint/no-explicit-any tags: Record, ): Promise { const tagTypeUUIDs = Object.keys(tags); @@ -439,7 +641,8 @@ export class FileService implements OnModuleInit { .leftJoin('tag.tagType', 'tagtype'); const tagWhereClauses: string[] = []; - const tagParameters = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const tagParameters: Record = {}; let validTagCount = 0; for (const uuid of tagTypeUUIDs) { @@ -449,7 +652,9 @@ export class FileService implements OnModuleInit { continue; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const value = tags[uuid]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const [column, processedValue] = this._getTagColumnAndValue( tagtype.datatype, value, @@ -461,7 +666,9 @@ export class FileService implements OnModuleInit { } // Create unique parameter names for this condition + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions const uuidParameter = `tagtype${validTagCount}`; + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions const valueParameter = `tagval${validTagCount}`; // Build the clause: (tagtype.uuid = :uuid AND tag.VALUE_COLUMN = :value) @@ -469,6 +676,7 @@ export class FileService implements OnModuleInit { `(tagtype.uuid = :${uuidParameter} AND tag.${column} = :${valueParameter})`, ); tagParameters[uuidParameter] = uuid; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment tagParameters[valueParameter] = processedValue; validTagCount++; @@ -550,13 +758,14 @@ export class FileService implements OnModuleInit { where: { file: { uuid: fileUuid }, }, - relations: ['actor'], + relations: ['actor', 'action', 'action.template', 'action.creator'], order: { createdAt: 'DESC' }, }); return { count: events.length, data: + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition events.map((event) => ({ uuid: event.uuid, type: event.type, @@ -566,6 +775,81 @@ export class FileService implements OnModuleInit { ? { uuid: event.actor.uuid, name: event.actor.name, + avatarUrl: null, + email: null, + } + : undefined, + action: event.action + ? { + uuid: event.action.uuid, + + name: event.action.template?.name, + + creator: event.action.creator + ? { + uuid: event.action.creator.uuid, + + name: event.action.creator.name, + avatarUrl: null, + email: null, + } + : undefined, + } + : undefined, + })) ?? [], + } as FileEventsDto; + } + + async getActionFileEvents(actionUuid: string): Promise { + const events = await this.eventRepo.find({ + where: { + action: { uuid: actionUuid }, + }, + relations: [ + 'actor', + 'action', + 'action.template', + 'file', + 'file.mission', + 'file.mission.project', + ], + order: { createdAt: 'DESC' }, + }); + + return { + count: events.length, + data: + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + events.map((event) => ({ + uuid: event.uuid, + type: event.type, + createdAt: event.createdAt, + details: event.details, + actor: event.actor + ? { + uuid: event.actor.uuid, + name: event.actor.name, + avatarUrl: null, + email: null, + } + : undefined, + action: event.action + ? { + uuid: event.action.uuid, + + name: event.action.template?.name, + } + : undefined, + file: event.file + ? { + uuid: event.file.uuid, + filename: event.file.filename, + missionUuid: event.file.mission?.uuid ?? '', + missionName: event.file.mission?.name ?? '', + projectUuid: + event.file.mission?.project?.uuid ?? '', + projectName: + event.file.mission?.project?.name ?? '', } : undefined, })) ?? [], @@ -584,6 +868,7 @@ export class FileService implements OnModuleInit { uuid: string, file: UpdateFile, actor?: UserEntity, + action?: ActionEntity, ): Promise { logger.debug(`Updating file with uuid: ${uuid}`); @@ -596,7 +881,6 @@ export class FileService implements OnModuleInit { if (!databaseFile.mission.project) throw new Error('Project not found!'); - // [10x] Detect Rename const oldFilename = databaseFile.filename; const isRenamed = file.filename !== oldFilename; @@ -621,9 +905,11 @@ export class FileService implements OnModuleInit { where: { uuid: file.missionUuid }, relations: ['project'], }); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (newMission) databaseFile.mission = newMission; } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (file.categories) { databaseFile.categories = await this.categoryRepository.find({ where: { uuid: In(file.categories) }, @@ -649,6 +935,7 @@ export class FileService implements OnModuleInit { filename: databaseFile.filename, missionUuid: databaseFile.mission.uuid, ...(actor ? { actor } : {}), + ...(action ? { action } : {}), details: { oldFilename, newFilename: file.filename }, }, true, @@ -693,14 +980,17 @@ export class FileService implements OnModuleInit { * Generate a download link for a file with the given uuid. * The link will expire after 1 week if expires is set to true. * + // eslint-disable-next-line @typescript-eslint/naming-convention * @param uuid The unique identifier of the file * @param expires Whether the download link should expire */ async generateDownload( uuid: string, expires: boolean, + // eslint-disable-next-line @typescript-eslint/naming-convention preview_only: boolean, actor?: UserEntity, + action?: ActionEntity, ): Promise { // verify that an uuid is provided if (!uuid || uuid === '') @@ -708,9 +998,11 @@ export class FileService implements OnModuleInit { const file = await this.fileRepository.findOneOrFail({ where: { uuid }, + relations: ['mission'], }); // verify that the file exists in DB + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (file.uuid === undefined || file.uuid !== uuid) throw new BadRequestException('File not found'); @@ -733,6 +1025,7 @@ export class FileService implements OnModuleInit { missionUuid: file.mission?.uuid ?? '', details: { expiresIn: expires ? '4 hours' : '1 week' }, ...(actor ? { actor } : {}), + ...(action ? { action } : {}), }, true, ); @@ -744,6 +1037,7 @@ export class FileService implements OnModuleInit { expires ? 4 * 60 * 60 : 604_800, { // set filename in response headers + // eslint-disable-next-line @typescript-eslint/naming-convention 'response-content-disposition': `attachment; filename ="${file.filename}"`, }, ); @@ -763,6 +1057,7 @@ export class FileService implements OnModuleInit { fileUUIDs: string[], missionUUID: string, actor?: UserEntity, + action?: ActionEntity, ): Promise { await Promise.all( fileUUIDs.map(async (uuid) => { @@ -785,6 +1080,7 @@ export class FileService implements OnModuleInit { filename: file.filename, missionUuid: missionUUID, ...(actor ? { actor } : {}), + ...(action ? { action } : {}), details: { fromMission: oldMissionUuid, toMission: missionUUID, @@ -808,6 +1104,7 @@ export class FileService implements OnModuleInit { }, ); } catch (error) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions logger.error(`Error moving file ${uuid}: ${error}`); } }), @@ -821,13 +1118,15 @@ export class FileService implements OnModuleInit { * @param uuid The unique identifier of the file * @param actor */ - async deleteFile(uuid: string, actor?: UserEntity): Promise { + async deleteFile( + uuid: string, + actor?: UserEntity, + action?: ActionEntity, + ): Promise { if (!uuid) throw new BadRequestException('UUID is required'); logger.debug(`Deleting file with uuid: ${uuid}`); - // [10x] Log Deletion Intent BEFORE strict deletion - // We need to fetch it first to have the metadata for the log const file = await this.fileRepository.findOne({ where: { uuid }, relations: ['mission'], @@ -841,6 +1140,7 @@ export class FileService implements OnModuleInit { filename: file.filename, missionUuid: file.mission?.uuid ?? '', ...(actor ? { actor } : {}), + ...(action ? { action } : {}), details: { snapshot: 'File deleted from DB and Storage' }, }, true, @@ -871,13 +1171,22 @@ export class FileService implements OnModuleInit { } async getStorage(): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const metrics = await this.storageService.getSystemMetrics(); return { - usedBytes: metrics.minio_system_drive_used_bytes[0].value, - totalBytes: metrics.minio_system_drive_total_bytes[0].value, - usedInodes: metrics.minio_system_drive_used_inodes[0].value, - totalInodes: metrics.minio_system_drive_total_inodes[0].value, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + usedBytes: metrics.minio_system_drive_used_bytes?.[0]?.value ?? 0, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + totalBytes: metrics.minio_system_drive_total_bytes?.[0]?.value ?? 0, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + usedInodes: metrics.minio_system_drive_used_inodes?.[0]?.value ?? 0, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + totalInodes: + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + metrics.minio_system_drive_total_inodes?.[0]?.value ?? 0, } as StorageOverviewDto; } @@ -910,6 +1219,8 @@ export class FileService implements OnModuleInit { filenames: string[], missionUUID: string, userUUID: string, + action?: ActionEntity, + uploadSource = 'Web Interface', ): Promise { const mission = await this.missionRepository.findOneOrFail({ where: { uuid: missionUUID }, @@ -919,8 +1230,34 @@ export class FileService implements OnModuleInit { where: { uuid: userUUID }, }); - const credentials = await Promise.all( - filenames.map(async (filename) => { + return await this.dataSource.transaction(async (manager) => { + // Deduplicate filenames to avoid self-collisions + const uniqueFilenames = [...new Set(filenames)]; + const credentials: { + bucket: string | null; + fileName: string; + fileUUID: string | null; + accessCredentials: Credentials | null; + error?: string | null; + }[] = []; + + const invalidFiles: { filename: string; error: string }[] = []; + + // Check for existing files first to avoid transaction abortion on duplicate key error + const existingFiles = await manager.find(FileEntity, { + where: { + filename: In(uniqueFilenames), + mission: { + uuid: missionUUID, + }, + }, + }); + + const existingFilenames = new Set( + existingFiles.map((f) => f.filename), + ); + + for (const filename of uniqueFilenames) { const emptyCredentials: { bucket: string | null; fileName: string; @@ -929,14 +1266,13 @@ export class FileService implements OnModuleInit { error: string | null; queueUUID?: string; } = { - // eslint-disable-next-line unicorn/no-null bucket: null, fileName: filename, - // eslint-disable-next-line unicorn/no-null + fileUUID: null, - // eslint-disable-next-line unicorn/no-null + accessCredentials: null, - // eslint-disable-next-line unicorn/no-null + error: null, }; @@ -945,14 +1281,16 @@ export class FileService implements OnModuleInit { FileType > = new Map([ ['.bag', FileType.BAG], + ['.mcap', FileType.MCAP], ['.yaml', FileType.YAML], - ['.yml', FileType.YML], + ['.yml', FileType.YAML], ['.svo2', FileType.SVO2], ['.tum', FileType.TUM], ['.db3', FileType.DB3], ]); + // eslint-disable-next-line @typescript-eslint/naming-convention const supported_file_endings = [ ...fileExtensionToFileTypeMap.keys(), ]; @@ -963,7 +1301,8 @@ export class FileService implements OnModuleInit { ) ) { emptyCredentials.error = 'Invalid file ending'; - return emptyCredentials; + credentials.push(emptyCredentials); + continue; } const matchingFileType = supported_file_endings.find((ending) => @@ -976,66 +1315,99 @@ export class FileService implements OnModuleInit { if (fileType === undefined) throw new UnsupportedMediaTypeException(); - // check if file already exists - const existingFile = await this.fileRepository.exists({ - where: { + if (existingFilenames.has(filename)) { + invalidFiles.push({ filename, - mission: { - uuid: missionUUID, - }, - }, - }); - if (existingFile) { - emptyCredentials.error = 'File already exists'; - emptyCredentials.fileName = filename; - return emptyCredentials; + error: 'File already exists', + }); + continue; } - const file = await this.fileRepository.save( - this.fileRepository.create({ - date: new Date(), - size: 0, - filename, - mission, - creator: user, - type: fileType, - state: FileState.UPLOADING, - origin: FileOrigin.UPLOAD, - }), - ); + try { + // Use a nested transaction (savepoint) for each file + await manager.transaction(async (nestedManager) => { + const file = await nestedManager.save( + FileEntity, + nestedManager.create(FileEntity, { + date: new Date(), + size: 0, + filename, + mission, + creator: user, + type: fileType, + state: FileState.UPLOADING, + origin: FileOrigin.UPLOAD, + }), + ); - await this.auditService.log( - FileEventType.UPLOAD_STARTED, - { - fileUuid: file.uuid, - filename: file.filename, - missionUuid: missionUUID, - actor: user, - details: { origin: FileOrigin.UPLOAD }, - }, - true, - ); + await this.auditService.log( + FileEventType.UPLOAD_STARTED, + { + fileUuid: file.uuid, + filename: file.filename, + missionUuid: missionUUID, + actor: user, + ...(action ? { action } : {}), + details: { + origin: FileOrigin.UPLOAD, + source: uploadSource, + }, + }, + true, + ); - return { - bucket: env.MINIO_DATA_BUCKET_NAME, - fileUUID: file.uuid, - fileName: filename, - accessCredentials: - await this.storageService.generateTemporaryCredential( - file.uuid, - env.MINIO_DATA_BUCKET_NAME, - ), - }; - }), - ); + credentials.push({ + bucket: env.MINIO_DATA_BUCKET_NAME, + fileUUID: file.uuid, + fileName: filename, + accessCredentials: + await this.storageService.generateTemporaryCredential( + file.uuid, + env.MINIO_DATA_BUCKET_NAME, + ), + }); + + // Add to local set to catch duplicates in the same batch + existingFilenames.add(filename); + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if ( + error instanceof QueryFailedError && + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + error.driverError.code === '23505' + ) { + invalidFiles.push({ + filename, + error: 'File already exists', + }); + // Also add to set so we don't try again if it appears again in list (though deduplication handles this) + existingFilenames.add(filename); + } else { + throw error; + } + } + } - return { - // TODO: fix typing - data: credentials as unknown as TemporaryFileAccessDto[], - count: credentials.length, - skip: 0, - take: credentials.length, - }; + if (invalidFiles.length > 0) { + // If we have some valid credentials and some errors, we might want to return partial success? + // The current API contract seems to throw BadRequest if ANY file is invalid. + // We'll stick to that behavior for now. + throw new BadRequestException({ + message: 'Validation failed', + errors: invalidFiles, + }); + } + + return { + // TODO: fix typing + // @ts-ignore + data: credentials, + count: credentials.length, + skip: 0, + take: credentials.length, + }; + }); } async cancelUpload( @@ -1055,6 +1427,8 @@ export class FileService implements OnModuleInit { fileUUIDs: string[], missionUUID: string, ): Promise { + if (fileUUIDs.length === 0) return; + const uniqueFilesUuids = [...new Set(fileUUIDs)]; await this.fileRepository.manager.transaction( @@ -1080,6 +1454,14 @@ export class FileService implements OnModuleInit { ); } + // Delete potentially running ingestion jobs + await transactionalEntityManager.softDelete( + IngestionJobEntity, + { + identifier: In(uniqueDatabaseFilesUuids), + }, + ); + await Promise.all( files.map(async (file) => { const bucket = env.MINIO_DATA_BUCKET_NAME; @@ -1093,7 +1475,10 @@ export class FileService implements OnModuleInit { }), ); - await transactionalEntityManager.remove(files); + await transactionalEntityManager.softDelete( + FileEntity, + uniqueDatabaseFilesUuids, + ); }, ); } @@ -1159,6 +1544,7 @@ export class FileService implements OnModuleInit { if (stats) { file.size = stats.size; logger.debug( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition `Updated size for ${file.filename}: ${file.size?.toString()}`, ); } else { @@ -1182,6 +1568,7 @@ export class FileService implements OnModuleInit { .select(['file.uuid', 'file.filename']) .getMany(); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions logger.debug(`Found ${filesToFix.length} bag files missing topics.`); for (const file of filesToFix) { diff --git a/backend/src/services/foxglove.service.ts b/backend/src/services/foxglove.service.ts new file mode 100644 index 000000000..cb3ed160e --- /dev/null +++ b/backend/src/services/foxglove.service.ts @@ -0,0 +1,127 @@ +import { FileAuditService } from '@kleinkram/backend-common/audit/file-audit.service'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import environment from '@kleinkram/backend-common/environment'; +import { StorageService } from '@kleinkram/backend-common/modules/storage/storage.service'; +import { FileEventType, FileType } from '@kleinkram/shared'; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import * as crypto from 'node:crypto'; +import { Repository } from 'typeorm'; +import logger from '../logger'; + +/** + * Service for generating and resolving Foxglove file URLs. + * + * These URLs allow temporary access to files for Foxglove Studio. + * We cannot use the default presigned URLs directly because Foxglove + * requires a specific URL format ending with `../filename.{bag|mcap}`. + * + */ +@Injectable() +export class FoxgloveService { + private readonly LINK_SECRET = environment.JWT_SECRET; + private readonly BASE_URL = environment.BACKEND_URL; + + constructor( + @InjectRepository(FileEntity) + private fileRepository: Repository, + private readonly storageService: StorageService, + private readonly auditService: FileAuditService, + ) {} + + async generateFoxgloveUrl(uuid: string, user: UserEntity): Promise { + logger.debug(`Generating Foxglove URL for file UUID: ${uuid}`); + const file = await this.fileRepository.findOneOrFail({ + where: { uuid }, + }); + + // only allow MCAP and BAG files + const allowedTypes = [FileType.MCAP, FileType.BAG]; + if (!allowedTypes.includes(file.type)) { + throw new BadRequestException( + `Foxglove URLs can only be generated for MCAP or BAG files. Found file type: ${FileType[file.type]}`, + ); + } + + // additional check based on file extension + const allowedExtensions = ['.mcap', '.bag']; + if ( + !allowedExtensions.some((extension) => + file.filename.toLowerCase().endsWith(extension), + ) + ) { + throw new BadRequestException( + `Foxglove URLs can only be generated for files with .mcap or .bag extension. Found file: ${file.filename}`, + ); + } + + // Audit the URL generation + await this.auditService.log( + FileEventType.FOXGLOVE_URL_GENERATED, + { + fileUuid: file.uuid, + filename: file.filename, + missionUuid: file.mission?.uuid ?? '', + actor: user, + }, + true, + ); + + // Generate signed URL valid for 4 hours + return this.generateSignedUrl(file.uuid, file.filename, user.uuid); + } + + async resolveRedirectUrl( + uuid: string, + expires: number, + signature: string, + userUuid: string, + ): Promise { + if (Date.now() > expires) throw new BadRequestException('Link expired'); + + const file = await this.fileRepository.findOneOrFail({ + where: { uuid }, + }); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const dataToSign = `${uuid}:${file.filename}:${expires}:${userUuid}`; + const expectedSignature = crypto + .createHmac('sha256', this.LINK_SECRET) + .update(dataToSign) + .digest('hex'); + + if (signature !== expectedSignature) + throw new BadRequestException('Invalid signature'); + + return await this.storageService.getPresignedDownloadUrl( + environment.MINIO_DATA_BUCKET_NAME, + file.uuid, + 3600, + { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'response-content-disposition': `attachment; filename ="${file.filename}"`, + }, + ); + } + + private generateSignature(data: string): string { + return crypto + .createHmac('sha256', this.LINK_SECRET) + .update(data) + .digest('hex'); + } + + private generateSignedUrl( + fileUuid: string, + filename: string, + userUuid: string, + ): string { + const expires = Date.now() + 4 * 60 * 60 * 1000; + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const dataToSign = `${fileUuid}:${filename}:${expires}:${userUuid}`; + const signature = this.generateSignature(dataToSign); + const cleanFilename = filename.replaceAll(/[^a-zA-Z0-9.-]/g, '_'); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + return `${this.BASE_URL}/integrations/foxglove/${fileUuid}/${cleanFilename}?expires=${expires}&signature=${signature}&u=${userUuid}`; + } +} diff --git a/backend/src/services/mission.service.ts b/backend/src/services/mission.service.ts index 0b039d40a..9f3a6496b 100644 --- a/backend/src/services/mission.service.ts +++ b/backend/src/services/mission.service.ts @@ -1,31 +1,31 @@ -import { CreateMission } from '@common/api/types/create-mission.dto'; import { + addAccessConstraints, + addAccessConstraintsToMissionQuery, +} from '@/endpoints/auth/auth-helper'; +import { AuthHeader } from '@/endpoints/auth/parameter-decorator'; +import { + missionEntityToDtoWithCreator, + missionEntityToDtoWithFiles, + missionEntityToFlatDto, + missionEntityToMinimumDto, +} from '@/serialization'; +import { + CreateMission, FlatMissionDto, MinimumMissionsDto, MissionsDto, MissionWithFilesDto, -} from '@common/api/types/mission/mission.dto'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { UserRole } from '@common/frontend_shared/enum'; -import { StorageService } from '@common/modules/storage/storage.service'; +} from '@kleinkram/api-dto'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { TagTypeEntity } from '@kleinkram/backend-common/entities/tagType/tag-type.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { StorageService } from '@kleinkram/backend-common/modules/storage/storage.service'; +import { UserRole } from '@kleinkram/shared'; import { ConflictException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { ILike, Not, Repository } from 'typeorm'; -import { - addAccessConstraints, - addAccessConstraintsToMissionQuery, -} from '../endpoints/auth/auth-helper'; -import { AuthHeader } from '../endpoints/auth/parameter-decorator'; import logger from '../logger'; -import { - missionEntityToDtoWithCreator, - missionEntityToDtoWithFiles, - missionEntityToFlatDto, - missionEntityToMinimumDto, -} from '../serialization'; import { TagService } from './tag.service'; import { UserService } from './user.service'; import { @@ -35,8 +35,8 @@ import { addSort, } from './utilities'; -import { SortOrder } from '@common/api/types/pagination'; -import env from '@common/environment'; +import { SortOrder } from '@kleinkram/api-dto'; +import env from '@kleinkram/backend-common/environment'; const FIND_MANY_SORT_KEYS = { missionName: 'mission.name', @@ -76,8 +76,10 @@ export class MissionService { if (!createMission.ignoreTags) { const missingTags = project.requiredTags.filter( (tagType: TagTypeEntity) => + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition createMission.tags[tagType.uuid] === undefined && createMission.tags[tagType.uuid] === '' && + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition createMission.tags[tagType.uuid] === null, ); if (missingTags.length > 0) { @@ -220,6 +222,7 @@ export class MissionService { .leftJoinAndSelect('mission.tags', 'tag') .leftJoinAndSelect('tag.tagType', 'tagType') .where('mission.uuid IN (:...missionIds)', { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access missionIds: missionIds.map((m) => m.mission_uuid), }); @@ -244,10 +247,16 @@ export class MissionService { { fileCount: number; fileSize: number } >(); for (const raw of rawResults) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const missionUuid = raw.mission_uuid; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument if (!statsMap.has(missionUuid)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument statsMap.set(missionUuid, { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access fileCount: Number.parseInt(raw.fileCount) || 0, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access fileSize: Number.parseInt(raw.fileSize) || 0, }); } @@ -361,10 +370,12 @@ export class MissionService { const { raw, entities } = await query.getRawAndEntities(); // this is necessary as raw and entities at not of the same length / order - // eslint-disable-next-line unicorn/no-array-reduce + // eslint-disable-next-line unicorn/no-array-reduce, @typescript-eslint/no-unsafe-assignment const rawLookup = raw.reduce( ( lookup: Record, + + // eslint-disable-next-line @typescript-eslint/naming-convention rawEntry: { mission_uuid: string }, ) => { lookup[rawEntry.mission_uuid] = rawEntry; @@ -375,10 +386,16 @@ export class MissionService { return { data: entities.map((m) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const rawEntry = rawLookup[m.uuid]; return { + // eslint-disable-next-line @typescript-eslint/no-misused-spread ...missionEntityToDtoWithCreator(m), + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access filesCount: rawEntry?.fileCount, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access size: Number.parseInt(rawEntry?.totalSize), }; }), @@ -409,7 +426,8 @@ export class MissionService { } await this.missionRepository.update(missionUUID, { - project: project, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + project: { uuid: project.uuid } as any, }); if (mission.files === undefined) throw new Error('Files not loaded'); @@ -496,6 +514,7 @@ export class MissionService { 4 * 60 * 60, { // set filename in response headers + // eslint-disable-next-line @typescript-eslint/naming-convention 'response-content-disposition': `attachment; filename ="${f.filename}"`, }, ), diff --git a/backend/src/services/project-guard.service.ts b/backend/src/services/project-guard.service.ts index 35738ca57..5258c5876 100644 --- a/backend/src/services/project-guard.service.ts +++ b/backend/src/services/project-guard.service.ts @@ -1,12 +1,12 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import UserEntity from '@common/entities/user/user.entity'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { ProjectAccessViewEntity } from '@kleinkram/backend-common/viewEntities/project-access-view.entity'; import { AccessGroupRights, AccessGroupType, UserRole, -} from '@common/frontend_shared/enum'; -import { ProjectAccessViewEntity } from '@common/viewEntities/project-access-view.entity'; +} from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { MoreThanOrEqual, Repository } from 'typeorm'; @@ -28,6 +28,7 @@ export class ProjectGuardService { projectUuid: string, rights: AccessGroupRights = AccessGroupRights.READ, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!projectUuid || !user) { return false; } @@ -55,6 +56,7 @@ export class ProjectGuardService { projectName: string, rights: AccessGroupRights = AccessGroupRights.READ, ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!projectName || !user) { return false; } @@ -68,6 +70,7 @@ export class ProjectGuardService { } async canCreate(user: UserEntity): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!user) { return false; } diff --git a/backend/src/services/project.service.ts b/backend/src/services/project.service.ts index d2373cc74..b328364fc 100644 --- a/backend/src/services/project.service.ts +++ b/backend/src/services/project.service.ts @@ -1,6 +1,14 @@ -import { CreateProject } from '@common/api/types/create-project.dto'; -import ProjectEntity from '@common/entities/project/project.entity'; -import UserEntity from '@common/entities/user/user.entity'; +import { addAccessConstraintsToProjectQuery } from '@/endpoints/auth/auth-helper'; +import { + CreateProject, + DefaultRightDto, + DefaultRights, + ProjectDto, + ProjectsDto, + ProjectWithRequiredTagsDto, + ResentProjectDto, + SortOrder, +} from '@kleinkram/api-dto'; import { BadRequestException, ConflictException, @@ -9,7 +17,6 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DataSource, EntityManager, ILike, Not, Repository } from 'typeorm'; -import { addAccessConstraintsToProjectQuery } from '../endpoints/auth/auth-helper'; import { UserService } from './user.service'; import { @@ -19,29 +26,27 @@ import { addSort, } from './utilities'; -import { DefaultRightDto } from '@common/api/types/access-control/default-right.dto'; -import { DefaultRights } from '@common/api/types/access-control/default-rights'; -import { SortOrder } from '@common/api/types/pagination'; -import { ProjectDto } from '@common/api/types/project/base-project.dto'; -import { ProjectWithRequiredTagsDto } from '@common/api/types/project/project-with-required-tags.dto'; -import { ProjectsDto } from '@common/api/types/project/projects.dto'; -import { ResentProjectDto } from '@common/api/types/project/recent-projects.dto'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import ProjectAccessEntity from '@common/entities/auth/project-access.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; +import { AuthHeader } from '@/endpoints/auth/parameter-decorator'; +import { + projectEntityToDto, + projectEntityToDtoWithMissionCountAndTags, + projectEntityToDtoWithRequiredTags, +} from '@/serialization'; import { + AccessGroupEntity, + MissionEntity, + ProjectAccessEntity, + ProjectEntity, + TagTypeEntity, + UserEntity, +} from '@kleinkram/backend-common'; +import { + AccessGroupConfig, AccessGroupRights, AccessGroupType, UserRole, -} from '@common/frontend_shared/enum'; +} from '@kleinkram/shared'; import { ConfigService } from '@nestjs/config'; -import { AuthHeader } from '../endpoints/auth/parameter-decorator'; -import { - projectEntityToDtoWithMissionCountAndTags, - projectEntityToDtoWithRequiredTags, -} from '../serialization'; -import { AccessGroupConfig } from '../types/access-group-config'; const FIND_MANY_SORT_KEYS = { projectName: 'project.name', @@ -69,8 +74,10 @@ export class ProjectService { private configService: ConfigService, private readonly dataSource: DataSource, ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const config = this.configService.get('accessConfig'); if (config === undefined) throw new Error('Access config not found'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.config = config; } @@ -154,6 +161,7 @@ export class ProjectService { // Get all Projects and add the computed field latestUpdate // LatestUpdate is computed in the subquery by selecting the latest updatedAt of the project, missions and files // This is implemented in SQL as TypeORM does not support sorting by a computed field... + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment projects = await this.projectRepository.query( 'SELECT DISTINCT\n' + ' "project"."uuid" AS "projectUuid",\n' + @@ -205,6 +213,7 @@ export class ProjectService { } if (user.role !== UserRole.ADMIN) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment projects = await this.projectRepository.query( 'SELECT DISTINCT\n' + ' "project"."uuid" AS "projectUuid",\n' + @@ -258,20 +267,33 @@ export class ProjectService { [AccessGroupRights.READ, user.uuid, take], ); } - return projects - .map((project: any) => { - return { - name: project.project_name as string, - uuid: project.projectUuid as string, - description: project.project_description as string, - updatedAt: project.latestUpdate as Date, - createdAt: project.project_createdAt as Date, - } as ResentProjectDto; - }) - .sort( - (a: ResentProjectDto, b: ResentProjectDto) => - b.updatedAt.getTime() - a.updatedAt.getTime(), - ); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return ( + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + projects + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + .map((project: any) => { + return { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + name: project.project_name as string, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + uuid: project.projectUuid as string, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + description: project.project_description as string, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + updatedAt: project.latestUpdate as Date, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + createdAt: project.project_createdAt as Date, + } as ResentProjectDto; + }) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + .sort( + (a: ResentProjectDto, b: ResentProjectDto) => + b.updatedAt.getTime() - a.updatedAt.getTime(), + ) + ); } async create( @@ -322,6 +344,7 @@ export class ProjectService { const accessGroupsDefaultIds = new Set( defaultAccessGroups .map((ag) => ag.uuid) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition .filter((id) => id !== undefined), ); @@ -367,9 +390,10 @@ export class ProjectService { }, ); - return (await this.projectRepository.findOneOrFail({ + const createdProject = await this.projectRepository.findOneOrFail({ where: { uuid: transactedProject.uuid }, - })) as unknown as ProjectDto; + }); + return projectEntityToDto(createdProject); } async update(uuid: string, project: CreateProject): Promise { @@ -389,9 +413,10 @@ export class ProjectService { ? {} : { autoConvert: project.autoConvert }), }); - return (await this.projectRepository.findOneOrFail({ + const updatedProject = await this.projectRepository.findOneOrFail({ where: { uuid }, - })) as unknown as ProjectDto; + }); + return projectEntityToDto(updatedProject); } async addTagType(uuid: string, tagTypeUUID: string): Promise { @@ -441,6 +466,7 @@ export class ProjectService { if (missionCount > 0) { throw new ConflictException( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Project has ${missionCount} missions. Please delete them first.`, ); } @@ -467,6 +493,7 @@ export class ProjectService { project: ProjectEntity, removedDefaultGroups?: string[], ): Promise<(ProjectAccessEntity | undefined)[]> { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (!removedDefaultGroups) { removedDefaultGroups = []; } diff --git a/backend/src/services/queue.service.ts b/backend/src/services/queue.service.ts index e5a9f3bb3..c98e069f4 100644 --- a/backend/src/services/queue.service.ts +++ b/backend/src/services/queue.service.ts @@ -1,47 +1,46 @@ -import { CancelProcessingResponseDto } from '@common/api/types/cancel-processing-response.dto'; -import { DeleteMissionResponseDto } from '@common/api/types/delete-mission-response.dto'; -import { DriveCreate } from '@common/api/types/drive-create.dto'; -import { UpdateTagTypeDto } from '@common/api/types/update-tag-type.dto'; -import { FileAuditService } from '@common/audit/file-audit.service'; -import { redis } from '@common/consts'; -import FileEntity from '@common/entities/file/file.entity'; -import IngestionJobEntity from '@common/entities/file/ingestion-job.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import env from '@common/environment'; +import { addAccessConstraints } from '@/endpoints/auth/auth-helper'; +import { + CancelProcessingResponseDto, + DeleteMissionResponseDto, + DriveCreate, +} from '@kleinkram/api-dto'; +import { FileAuditService } from '@kleinkram/backend-common/audit/file-audit.service'; +import { redis } from '@kleinkram/backend-common/consts'; +import { FileEntity } from '@kleinkram/backend-common/entities/file/file.entity'; +import { IngestionJobEntity } from '@kleinkram/backend-common/entities/file/ingestion-job.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import env from '@kleinkram/backend-common/environment'; +import { StorageService } from '@kleinkram/backend-common/modules/storage/storage.service'; import { FileEventType, FileLocation, FileOrigin, + FileSource, FileState, FileType, QueueState, UserRole, -} from '@common/frontend_shared/enum'; -import { StorageService } from '@common/modules/storage/storage.service'; -import { ConflictException, Injectable, OnModuleInit } from '@nestjs/common'; +} from '@kleinkram/shared'; +import { getGoogleDriveInfo } from '@kleinkram/validation'; +import { + BadRequestException, + ConflictException, + Injectable, + OnModuleInit, +} from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; import { InjectMetric } from '@willsoto/nestjs-prometheus'; import Queue from 'bull'; +import * as fs from 'node:fs'; import { Gauge } from 'prom-client'; import { FindOptionsWhere, In, IsNull, MoreThan, Repository } from 'typeorm'; -import { addAccessConstraints } from '../endpoints/auth/auth-helper'; import logger from '../logger'; import { UserService } from './user.service'; -function extractFileIdFromUrl(url: string): string | undefined { - const filePattern = /\/file(?:\/u\/\d+)?\/d\/([a-zA-Z0-9_-]+)/; - const folderPattern = /\/drive(?:\/u\/\d+)?\/folders\/([a-zA-Z0-9_-]+)/; - let match = filePattern.exec(url); - if (match?.[1]) return match[1]; - match = folderPattern.exec(url); - if (match?.[1]) return match[1]; - return undefined; -} - @Injectable() -class QueueService implements OnModuleInit { +export class QueueService implements OnModuleInit { private fileQueue!: Queue.Queue; constructor( @@ -74,20 +73,33 @@ class QueueService implements OnModuleInit { async importFromDrive( driveCreate: DriveCreate, user: UserEntity, - ): Promise { + ): Promise { const mission = await this.missionRepository.findOneOrFail({ where: { uuid: driveCreate.missionUUID }, }); const creator = await this.userService.findOneByUUID(user.uuid, {}, {}); - const fileId = extractFileIdFromUrl(driveCreate.driveURL); - if (fileId === undefined) - throw new ConflictException('Invalid Drive URL'); + const { id: fileId, isFolder } = getGoogleDriveInfo( + driveCreate.driveURL, + ); + if (fileId === null) throw new ConflictException('Invalid Drive URL'); + + if ( + isFolder && // Check if backend has Service Account Key configured + // We use fs.existsSync to be robust + (!env.GOOGLE_KEY_FILE || + !fs.existsSync(env.GOOGLE_KEY_FILE) || + !fs.statSync(env.GOOGLE_KEY_FILE).isFile()) + ) { + throw new BadRequestException( + 'Google Drive Folder ingestion requires a configured Service Account on the server.', + ); + } const queueEntry = await this.queueRepository.save( this.queueRepository.create({ identifier: fileId, - display_name: `GoogleDrive Object (no id=${fileId})`, + displayName: `Google Drive File (${fileId})`, state: QueueState.AWAITING_PROCESSING, location: FileLocation.DRIVE, mission, @@ -100,7 +112,6 @@ class QueueService implements OnModuleInit { .catch((error: unknown) => logger.error(error)); logger.debug('Added drive file to queue'); - return {}; } async recalculateHashes(): Promise<{ @@ -115,13 +126,15 @@ class QueueService implements OnModuleInit { relations: ['mission', 'mission.project'], }); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions logger.debug(`Add ${files.length} files to queue for hash calculation`); for (const file of files) { try { await this.fileQueue.add('extractHashFromMinio', { - file_uuid: file.uuid, + fileUuid: file.uuid, }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { logger.error(error); } @@ -134,6 +147,7 @@ class QueueService implements OnModuleInit { uuid: string, md5: string, actor: UserEntity, + source: FileSource | string = FileSource.WEB_INTERFACE, ): Promise { let job = await this.queueRepository.findOne({ where: { identifier: uuid }, @@ -142,7 +156,7 @@ class QueueService implements OnModuleInit { if (!job) { logger.warn( - `confirmUpload: Job missing for file ${uuid}. Recreating...`, + `confirmUpload: Job missing for file ${uuid}.Recreating...`, ); const file = await this.fileRepository.findOneOrFail({ @@ -153,7 +167,8 @@ class QueueService implements OnModuleInit { job = await this.queueRepository.save( this.queueRepository.create({ identifier: file.uuid, - display_name: file.filename, + + displayName: file.filename, state: QueueState.AWAITING_UPLOAD, location: FileLocation.MINIO, mission: file.mission, @@ -168,7 +183,8 @@ class QueueService implements OnModuleInit { ) { if (job.state >= QueueState.PROCESSING) return; logger.warn( - `Resuming upload for job ${job.uuid} in state ${job.state}`, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Resuming upload for job ${job.uuid} in state ${job.state} `, ); } @@ -187,6 +203,7 @@ class QueueService implements OnModuleInit { if (file.state === FileState.UPLOADING) file.state = FileState.OK; file.size = fileInfo.size; + file.hash = md5; await this.fileRepository.save(file); job.state = QueueState.AWAITING_PROCESSING; @@ -205,8 +222,9 @@ class QueueService implements OnModuleInit { fileUuid: file.uuid, filename: file.filename, ...(job.mission?.uuid ? { missionUuid: job.mission.uuid } : {}), + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition ...(actor ? { actor } : {}), - details: { origin: FileOrigin.UPLOAD }, + details: { origin: FileOrigin.UPLOAD, source }, }, true, ); @@ -218,7 +236,7 @@ class QueueService implements OnModuleInit { userUUID: string, skip: number, take: number, - ): Promise { + ): Promise { // @ts-ignore const user = await this.userService.findOneByUUID(userUUID); const where: FindOptionsWhere = { @@ -260,7 +278,7 @@ class QueueService implements OnModuleInit { .map((state) => Number.parseInt(state)); query.andWhere('ingestion_job.state IN (:...filter)', { filter }); } - return query.getMany(); + return (await query.getMany()) as IngestionJobEntity[]; } async delete( @@ -281,6 +299,7 @@ class QueueService implements OnModuleInit { const waitingJobs = await this.fileQueue.getWaiting(); const jobToRemove = waitingJobs.find( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (job) => job.data.queueUuid === queueUUID, ); if (jobToRemove) await jobToRemove.remove(); @@ -330,6 +349,7 @@ class QueueService implements OnModuleInit { const waitingJobs = await this.fileQueue.getWaiting(); const jobToRemove = waitingJobs.find( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (job) => job.data.queueUuid === queueUUID, ); if (jobToRemove) await jobToRemove.remove(); @@ -340,6 +360,35 @@ class QueueService implements OnModuleInit { return {}; } + async stopJob(queueUUID: string): Promise { + const queue = await this.queueRepository.findOneOrFail({ + where: { uuid: queueUUID }, + }); + + // We can only stop active jobs + if (queue.state !== QueueState.PROCESSING) { + throw new ConflictException('Job is not processing'); + } + + // To stop a job we need to find the active job in the queue + const activeJobs = await this.fileQueue.getActive(); + const jobToStop = activeJobs.find( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (job) => job.data.queueUuid === queueUUID, + ); + + if (jobToStop) { + // This moves the job to failed + await jobToStop.moveToFailed({ message: 'Stopped by user' }); + queue.state = QueueState.ERROR; + await this.queueRepository.save(queue); + } else { + throw new ConflictException( + 'Job not found in active queue, state might be desynced', + ); + } + } + /** * Updates Prometheus metrics for File Queue */ diff --git a/backend/src/services/tag.service.ts b/backend/src/services/tag.service.ts index 8b369adb8..c369b3585 100644 --- a/backend/src/services/tag.service.ts +++ b/backend/src/services/tag.service.ts @@ -1,10 +1,14 @@ -import { AddTagDto, AddTagsDto } from '@common/api/types/tags/add-tags.dto'; -import { DeleteTagDto } from '@common/api/types/tags/delete-tag.dto'; -import { TagTypeDto, TagTypesDto } from '@common/api/types/tags/tags.dto'; -import MetadataEntity from '@common/entities/metadata/metadata.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; -import { DataType } from '@common/frontend_shared/enum'; +import { + AddTagDto, + AddTagsDto, + DeleteTagDto, + TagTypeDto, + TagTypesDto, +} from '@kleinkram/api-dto'; +import { MetadataEntity } from '@kleinkram/backend-common/entities/metadata/metadata.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { TagTypeEntity } from '@kleinkram/backend-common/entities/tagType/tag-type.entity'; +import { DataType } from '@kleinkram/shared'; import { ConflictException, Injectable, @@ -79,6 +83,8 @@ export class TagService { } tag = this.tagRepository.create({ tagType, + + // eslint-disable-next-line @typescript-eslint/naming-convention value_number: value as number, mission, }); @@ -98,6 +104,7 @@ export class TagService { } tag = this.tagRepository.create({ tagType, + // eslint-disable-next-line @typescript-eslint/naming-convention value_string: value, mission, }); @@ -109,8 +116,10 @@ export class TagService { 'Value must be a string', ); } + tag = this.tagRepository.create({ tagType, + // eslint-disable-next-line @typescript-eslint/naming-convention value_location: value, mission, }); @@ -123,6 +132,7 @@ export class TagService { } tag = this.tagRepository.create({ tagType, + // eslint-disable-next-line @typescript-eslint/naming-convention value_boolean: value as boolean, mission, }); @@ -141,6 +151,7 @@ export class TagService { } tag = this.tagRepository.create({ tagType, + // eslint-disable-next-line @typescript-eslint/naming-convention value_date: new Date(value), mission, }); @@ -183,7 +194,9 @@ export class TagService { if (isString) { value = Number.parseInt(value as string); } - exsitingTag[tagType.datatype] = value as number; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (exsitingTag as any)[tagType.datatype] = value as number; break; } throw new UnprocessableEntityException( @@ -198,17 +211,9 @@ export class TagService { 'Value must be a string', ); } - exsitingTag[DataType.STRING] = value; - break; - } - case DataType.LOCATION: { - if (typeof value !== 'string') { - throw new UnprocessableEntityException( - 'Value must be a string', - ); - } - exsitingTag[tagType.datatype] = value; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (exsitingTag as any)[tagType.datatype] = value; break; } @@ -217,7 +222,9 @@ export class TagService { if (isString) { value = value === 'true'; } - exsitingTag[tagType.datatype] = value as boolean; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (exsitingTag as any)[tagType.datatype] = value as boolean; break; } @@ -231,7 +238,9 @@ export class TagService { 'Value must be a string', ); } - exsitingTag[tagType.datatype] = new Date(value); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (exsitingTag as any)[tagType.datatype] = new Date(value); break; } @@ -294,6 +303,7 @@ export class TagService { if ( type !== undefined && type !== DataType.ANY && + // eslint-disable-next-line @typescript-eslint/no-explicit-any (type as any) !== '' ) { where.datatype = type; diff --git a/backend/src/services/template.service.ts b/backend/src/services/template.service.ts new file mode 100644 index 000000000..c72d567ae --- /dev/null +++ b/backend/src/services/template.service.ts @@ -0,0 +1,279 @@ +import { AuthHeader } from '@/endpoints/auth/parameter-decorator'; +import { actionTemplateEntityToDto } from '@/serialization/action-template'; +import { + ActionTemplateDto, + ActionTemplatesDto, + CreateTemplateDto, + DeleteTemplateResponseDto, + UpdateTemplateDto, +} from '@kleinkram/api-dto'; +import { ActionTemplateEntity } from '@kleinkram/backend-common/entities/action/action-template.entity'; +import { ActionEntity } from '@kleinkram/backend-common/entities/action/action.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { + ConflictException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, QueryFailedError, Repository } from 'typeorm'; + +@Injectable() +export class TemplateService { + private readonly DOCKER_NAMESPACE = process.env.VITE_DOCKER_HUB_NAMESPACE; + + constructor( + @InjectRepository(ActionTemplateEntity) + private actionTemplateRepository: Repository, + @InjectRepository(ActionEntity) + private actionRepository: Repository, + @InjectRepository(UserEntity) + private userRepository: Repository, + ) {} + + async create( + data: CreateTemplateDto, + auth: AuthHeader, + ): Promise { + this.validateDockerNamespace(data.dockerImage); + + const template = this.actionTemplateRepository.create({ + // eslint-disable-next-line @typescript-eslint/no-misused-spread + ...data, + creator: { uuid: auth.user.uuid }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + image_name: data.dockerImage, + command: data.command ?? '', + entrypoint: data.entrypoint ?? '', + isArchived: false, + }); + + try { + const saved = await this.actionTemplateRepository.save(template); + const fullEntity = + await this.actionTemplateRepository.findOneOrFail({ + where: { uuid: saved.uuid }, + relations: { creator: true }, + }); + + return actionTemplateEntityToDto(fullEntity); + } catch (error) { + if ( + error instanceof QueryFailedError && + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + error.driverError?.code === '23505' + ) { + throw new ConflictException( + 'Template with this name already exists', + ); + } + throw error; + } + } + + async createVersion( + uuid: string, + data: UpdateTemplateDto, + auth: AuthHeader, + ): Promise { + const nextVersion = await this.calculateNextVersion(data.name); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + delete (data as any).uuid; + this.validateDockerNamespace(data.dockerImage); + + const newTemplate = this.actionTemplateRepository.create({ + // eslint-disable-next-line @typescript-eslint/no-misused-spread + ...data, + + description: data.description, + // eslint-disable-next-line @typescript-eslint/naming-convention + image_name: data.dockerImage, + creator: { uuid: auth.user.uuid }, + version: nextVersion, + command: data.command ?? '', + entrypoint: data.entrypoint ?? '', + isArchived: false, + }); + + const saved = await this.actionTemplateRepository.save(newTemplate); + + const fullEntity = await this.actionTemplateRepository.findOneOrFail({ + where: { uuid: saved.uuid }, + relations: { creator: true }, + }); + + return actionTemplateEntityToDto(fullEntity); + } + + async findAll( + skip: number, + take: number, + search?: string, + includeArchived?: boolean, + ): Promise { + const qb = this.actionTemplateRepository + .createQueryBuilder('template') + .leftJoinAndSelect('template.creator', 'creator') + .skip(skip) + .take(take) + .orderBy('template.name', 'ASC') + .addOrderBy('template.version', 'DESC'); + + if (!includeArchived) { + qb.andWhere('template.isArchived = :archived', { archived: false }); + } + + if (search) { + qb.andWhere( + new Brackets((sub) => { + sub.where('template.name ILIKE :searchTerm', { + searchTerm: `%${search}%`, + }).orWhere('template.image_name ILIKE :searchTerm', { + searchTerm: `%${search}%`, + }); + }), + ); + } + + qb.addSelect((subQuery) => { + return subQuery + .select('COUNT(action.uuid)', 'count') + .from(ActionEntity, 'action') + .leftJoin('action.template', 't') + .where('t.name = template.name'); + }, 'executionCount'); + + const { entities, raw } = await qb.getRawAndEntities(); + const count = await qb.getCount(); + + const data = entities.map((entity) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const rawRow = raw.find((r) => r.template_uuid === entity.uuid); + const execCount = rawRow + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + Number.parseInt(rawRow.executionCount, 10) + : 0; + + return actionTemplateEntityToDto(entity, execCount); + }); + + return { + count, + data, + skip, + take, + }; + } + + /** + * Delete or archive a template based on its usage. + * + * This will target all versions of the template with the same name. + * + * @param uuid + */ + async delete(uuid: string): Promise { + const template = await this.actionTemplateRepository.findOne({ + where: { uuid }, + select: ['name'], + }); + + if (!template) { + throw new NotFoundException('Template not found'); + } + + const { name } = template; + + const totalExecutionCount = await this.actionRepository + .createQueryBuilder('action') + .innerJoin('action.template', 'template') + .where('template.name = :name', { name }) + .getCount(); + + if (totalExecutionCount > 0) { + await this.actionTemplateRepository.update( + { name }, + { isArchived: true }, + ); + return { archived: true, deleted: false }; + } else { + await this.actionTemplateRepository.delete({ name }); + return { archived: false, deleted: true }; + } + } + + async findRevisions( + uuid: string, + skip: number, + take: number, + ): Promise { + const current = await this.actionTemplateRepository.findOne({ + where: { uuid }, + select: ['name'], + }); + + if (!current) { + throw new NotFoundException('Template not found'); + } + + const qb = this.actionTemplateRepository + .createQueryBuilder('template') + .leftJoinAndSelect('template.creator', 'creator') + .loadRelationCountAndMap( + 'template.executionCount', + 'template.actions', + ) + .where('template.name = :name', { name: current.name }) + .orderBy('template.version', 'DESC') + .skip(skip) + .take(take); + + const [entities, count] = await qb.getManyAndCount(); + + const data = entities.map((entity) => { + return actionTemplateEntityToDto( + entity, + entity.executionCount ?? 0, + ); + }); + + return { + count, + data, + skip, + take, + }; + } + + async isNameAvailable(name: string): Promise { + const count = await this.actionTemplateRepository.count({ + where: { name }, + }); + return count === 0; + } + + private validateDockerNamespace(imageName: string): void { + if ( + this.DOCKER_NAMESPACE && + !imageName.startsWith(this.DOCKER_NAMESPACE) + ) { + throw new ConflictException( + `Only images from the ${this.DOCKER_NAMESPACE} namespace are allowed`, + ); + } + } + + private async calculateNextVersion(name: string): Promise { + const lastVersionTemplate = await this.actionTemplateRepository.findOne( + { + where: { name }, + order: { version: 'DESC' }, + }, + ); + + const currentVersion = lastVersionTemplate?.version ?? 0; + return currentVersion + 1; + } +} diff --git a/backend/src/services/topic.service.ts b/backend/src/services/topic.service.ts index 5a4af1c32..4dfe3fc27 100644 --- a/backend/src/services/topic.service.ts +++ b/backend/src/services/topic.service.ts @@ -1,12 +1,12 @@ -import { TopicNamesDto, TopicsDto } from '@common/api/types/topic.dto'; -import TopicEntity from '@common/entities/topic/topic.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { UserRole } from '@common/frontend_shared/enum'; +import { addAccessConstraints } from '@/endpoints/auth/auth-helper'; +import { topicEntityToDto } from '@/serialization'; +import { TopicNamesDto, TopicsDto } from '@kleinkram/api-dto'; +import { TopicEntity } from '@kleinkram/backend-common/entities/topic/topic.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { UserRole } from '@kleinkram/shared'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { addAccessConstraints } from '../endpoints/auth/auth-helper'; -import { topicEntityToDto } from '../serialization'; @Injectable() export class TopicService { @@ -17,7 +17,7 @@ export class TopicService { private userRepository: Repository, ) {} - async findAllNames(userUuid): Promise { + async findAllNames(userUuid: string): Promise { const baseQuery = this.topicRepository .createQueryBuilder('topic') .select('DISTINCT topic.name', 'name') @@ -43,6 +43,8 @@ export class TopicService { return { count, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access data: topics.map((topic) => topic.name), take: count, skip: 0, @@ -81,6 +83,7 @@ export class TopicService { ).getManyAndCount(); return { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument data: topics.map((element) => topicEntityToDto(element)), count, take, diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index e1401005a..9edc2647f 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -1,23 +1,29 @@ -import { PermissionsDto } from '@common/api/types/permissions.dto'; +import { AuthHeader } from '@/endpoints/auth/parameter-decorator'; +import { + userEntityToCurrentAPIUserDto, + userEntityToDto, +} from '@/serialization'; import { CurrentAPIUserDto, + PermissionsDto, UserDto, UsersDto, -} from '@common/api/types/user.dto'; -import { systemUser } from '@common/consts'; -import ApikeyEntity from '@common/entities/auth/apikey.entity'; -import UserEntity from '@common/entities/user/user.entity'; +} from '@kleinkram/api-dto'; +import { + ApiKeyEntity, + MissionAccessViewEntity, + ProjectAccessViewEntity, + UserEntity, +} from '@kleinkram/backend-common'; +import { systemUser } from '@kleinkram/backend-common/consts'; import { AccessGroupRights, AccessGroupType, UserRole, -} from '@common/frontend_shared/enum'; -import { MissionAccessViewEntity } from '@common/viewEntities/mission-access-view.entity'; -import { ProjectAccessViewEntity } from '@common/viewEntities/project-access-view.entity'; +} from '@kleinkram/shared'; import { ForbiddenException, Injectable, OnModuleInit } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FindOptionsRelations, FindOptionsSelect, Repository } from 'typeorm'; -import { AuthHeader } from '../endpoints/auth/parameter-decorator'; @Injectable() export class UserService implements OnModuleInit { @@ -26,8 +32,8 @@ export class UserService implements OnModuleInit { private userRepository: Repository, @InjectRepository(ProjectAccessViewEntity) private projectAccessView: Repository, - @InjectRepository(ApikeyEntity) - private apikeyRepository: Repository, + @InjectRepository(ApiKeyEntity) + private apikeyRepository: Repository, @InjectRepository(MissionAccessViewEntity) private missionAccessView: Repository, ) {} @@ -79,26 +85,32 @@ export class UserService implements OnModuleInit { user.role = UserRole.ADMIN; await this.userRepository.save(user); - return user as unknown as CurrentAPIUserDto; + return userEntityToCurrentAPIUserDto(user); } async me(auth: AuthHeader): Promise { - return (await this.userRepository.findOneOrFail({ + const user = await this.userRepository.findOneOrFail({ where: { uuid: auth.user.uuid }, select: ['uuid', 'name', 'email', 'role', 'avatarUrl'], - relations: ['memberships', 'memberships.accessGroup'], - })) as unknown as CurrentAPIUserDto; + relations: [ + 'memberships', + 'memberships.accessGroup', + 'memberships.user', + ], + }); + + return userEntityToCurrentAPIUserDto(user); } async findAll(skip: number, take: number): Promise { - const [user, count] = await this.userRepository.findAndCount({ + const [users, count] = await this.userRepository.findAndCount({ skip, take, where: { hidden: false }, }); return { - users: user as UserDto[], + users: users.map((u) => userEntityToDto(u)), count, }; } @@ -109,7 +121,7 @@ export class UserService implements OnModuleInit { }); user.role = UserRole.ADMIN; await this.userRepository.save(user); - return user as unknown as UserDto; + return userEntityToDto(user); } async demoteUser(usermail: string) { @@ -118,7 +130,7 @@ export class UserService implements OnModuleInit { }); user.role = UserRole.USER; await this.userRepository.save(user); - return user as unknown as UserDto; + return userEntityToDto(user); } async search( @@ -127,6 +139,7 @@ export class UserService implements OnModuleInit { take: number, ): Promise { // Ensure the search string is not empty or null or less than 3 characters + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (search === null || search === '' || search.length < 3) { return { users: [], @@ -150,7 +163,7 @@ export class UserService implements OnModuleInit { .take(take) .getManyAndCount(); - const usersDto = users as UserDto[]; + const usersDto = users.map((u) => userEntityToDto(u, true)); // return the email only if it is an exact match // otherwise set it to null @@ -218,12 +231,12 @@ export class UserService implements OnModuleInit { // map project accesses const projectAccesses = projectAccessRows.map((r) => ({ uuid: r.projectUuid, - access: r.rights, + access: Number(r.rights), })); const missionAccesses = missionAccessRows.map((r) => ({ uuid: r.missionUuid, - access: r.rights, + access: Number(r.rights), })); return { @@ -243,8 +256,9 @@ export class UserService implements OnModuleInit { */ async findUserByAPIKey( apikey: string, - ): Promise<{ apiKey: ApikeyEntity; user: UserEntity }> { + ): Promise<{ apiKey: ApiKeyEntity; user: UserEntity }> { const user = await this.userRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/naming-convention where: { api_keys: { apikey } }, relations: ['api_keys'], select: ['uuid', 'name', 'role'], @@ -255,13 +269,14 @@ export class UserService implements OnModuleInit { // Disable global eager loading to ensure your manual relations take precedence loadEagerRelations: false, relations: { - action: true, + action: { + template: true, + }, mission: { project: true, }, }, }); - return { user, apiKey }; } diff --git a/backend/src/services/utilities.ts b/backend/src/services/utilities.ts index 63572902b..30a170b44 100644 --- a/backend/src/services/utilities.ts +++ b/backend/src/services/utilities.ts @@ -1,7 +1,7 @@ -import { SortOrder } from '@common/api/types/pagination'; -import File from '@common/entities/file/file.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; +import { SortOrder } from '@kleinkram/api-dto'; +import { FileEntity as File } from '@kleinkram/backend-common/entities/file/file.entity'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { ProjectEntity } from '@kleinkram/backend-common/entities/project/project.entity'; import { MethodNotAllowedException } from '@nestjs/common'; import { isValid, parseISO } from 'date-fns'; import { @@ -22,6 +22,7 @@ export const stringToBoolean = (value: string): boolean | undefined => { return false; } } + return undefined; }; export const stringToNumber = (value: string): number | undefined => { @@ -33,6 +34,7 @@ export const stringToNumber = (value: string): number | undefined => { if (!Number.isNaN(number)) { return number; } + return undefined; }; export const stringToDate = (value: string): Date | undefined => { @@ -41,6 +43,7 @@ export const stringToDate = (value: string): Date | undefined => { if (isValid(date)) { return date; } + return undefined; }; export const stringToLocation = (value: string): string | undefined => { @@ -99,6 +102,7 @@ const metadataMatchesKeyValuePair = ( * to the same query * */ + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (tok === undefined) { tok = uuidv4().replaceAll('-', ''); } @@ -338,8 +342,7 @@ export const addSort = ( } query.orderBy( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - allowedSortKeyMap[sortBy]!, + allowedSortKeyMap[sortBy], sortOrder === SortOrder.ASC ? 'ASC' : 'DESC', ); return query; diff --git a/backend/src/services/worker.service.ts b/backend/src/services/worker.service.ts index cde71c682..34e1b3eab 100644 --- a/backend/src/services/worker.service.ts +++ b/backend/src/services/worker.service.ts @@ -1,7 +1,9 @@ -import { ActionWorkersDto } from '@common/api/types/action-workers.dto'; -import WorkerEntity from '@common/entities/worker/worker.entity'; +import { ActionWorkersDto } from '@kleinkram/api-dto'; +import { WorkerEntity } from '@kleinkram/backend-common/entities/worker/worker.entity'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { plainToInstance } from 'class-transformer'; +import { validateSync } from 'class-validator'; import { Repository } from 'typeorm'; @Injectable() @@ -16,22 +18,34 @@ export class WorkerService { // deduplicate workers by hostname get last seen worker // eslint-disable-next-line unicorn/no-array-reduce - const workerMap = workers.reduce((accumulator, worker) => { - if ( - !accumulator[worker.hostname] || - accumulator[worker.hostname].lastSeen < worker.lastSeen - ) { - accumulator[worker.hostname] = worker; - } - return accumulator; - }, {}); + const workerMap = workers.reduce( + (accumulator: Record, worker) => { + if ( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + !accumulator[worker.hostname] || + accumulator[worker.hostname].lastSeen < worker.lastSeen + ) { + accumulator[worker.hostname] = worker; + } + return accumulator; + }, + {}, + ); const count = Object.keys(workerMap).length; - return { + const result = { count, - data: Object.values(workerMap), + data: Object.values(workerMap).map((worker) => ({ + // eslint-disable-next-line @typescript-eslint/no-misused-spread + ...worker, + gpuModel: worker.gpuModel ?? null, + })), skip: 0, take: count, }; + + const dto = plainToInstance(ActionWorkersDto, result); + validateSync(dto, { whitelist: true, forbidNonWhitelisted: false }); + return dto; } } diff --git a/backend/src/types/access-group-config.ts b/backend/src/types/access-group-config.ts deleted file mode 100644 index c715c025c..000000000 --- a/backend/src/types/access-group-config.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface AccessGroupConfig { - emails: [{ email: string; access_groups: string[] }]; - access_groups: [{ name: string; uuid: string; rights: number }]; -} diff --git a/backend/src/validation/body-decorators.ts b/backend/src/validation/body-decorators.ts deleted file mode 100644 index 7d702036c..000000000 --- a/backend/src/validation/body-decorators.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - applyDecorators, - BadRequestException, - createParamDecorator, - ExecutionContext, - InternalServerErrorException, -} from '@nestjs/common'; -import { ApiProperty } from '@nestjs/swagger'; -import { plainToInstance } from 'class-transformer'; -import { IsUUID, validateOrReject } from 'class-validator'; -import { metadataApplier } from './metadata-applier'; -import { StringValidate, UUIDValidate } from './validation-types'; - -export function ApiUUIDProperty(description = 'UUID'): PropertyDecorator { - return applyDecorators( - IsUUID(), - ApiProperty({ - description, - type: 'string', - format: 'uuid', - }), - ); -} - -export const BodyUUID = (parameterName: string, parameterDescription: string) => - createParamDecorator( - async (data: string, context: ExecutionContext) => { - if (!data) { - throw new InternalServerErrorException( - 'Parameter is missing field in controller', - ); - } - const request = context.switchToHttp().getRequest(); - const value = request.body[data]; - const object = plainToInstance(UUIDValidate, { value }); - await validateOrReject(object).catch(() => { - throw new BadRequestException( - 'Body parameter is not a valid UUID', - ); - }); - - return value; - }, - metadataApplier( - parameterName, - parameterDescription, - 'body', - 'uuid', - true, - ), - )(parameterName); - -export const BodyString = ( - parameterName: string, - parameterDescription: string, -) => - createParamDecorator( - async (data: string, context: ExecutionContext) => { - const request = context.switchToHttp().getRequest(); - const value = request.body[data]; - - const object = plainToInstance(StringValidate, { value }); - await validateOrReject(object).catch(() => { - throw new BadRequestException( - undefined, - 'Parameter is not a valid String', - ); - }); - - return value; - }, - metadataApplier( - parameterName, - parameterDescription, - 'body', - 'string', - true, - ), - )(parameterName); - -export const BodyNotNull = ( - parameterName: string, - parameterDescription: string, -) => - createParamDecorator( - (data: string, context: ExecutionContext) => { - const request = context.switchToHttp().getRequest(); - const value = request.body[data]; - - if (value === undefined || value === null) { - throw new BadRequestException('Parameter is empty'); - } - - return value; - }, - metadataApplier( - parameterName, - parameterDescription, - 'body', - 'unknown', - true, - ), - )(parameterName); - -export const BodyUUIDArray = ( - parameterName: string, - parameterDescription: string, -) => - createParamDecorator( - async (data: string, context: ExecutionContext) => { - const request = context.switchToHttp().getRequest(); - const value = request.body[data]; - - if (!Array.isArray(value)) { - throw new BadRequestException('Parameter is not an array'); - } - - for (const uuid of value) { - const object = plainToInstance(UUIDValidate, { value: uuid }); - await validateOrReject(object).catch(() => { - throw new BadRequestException( - 'Body parameter is not a valid UUID', - ); - }); - } - - return value; - }, - metadataApplier( - parameterName, - parameterDescription, - 'body', - 'uuid[]', - true, - ), - )(parameterName); diff --git a/backend/src/validation/parameter-decorators.ts b/backend/src/validation/parameter-decorators.ts index 627fcf5b2..fc1745fb7 100644 --- a/backend/src/validation/parameter-decorators.ts +++ b/backend/src/validation/parameter-decorators.ts @@ -1,3 +1,4 @@ +import { metadataApplier, UUIDValidate } from '@kleinkram/validation'; import { BadRequestException, createParamDecorator, @@ -5,8 +6,6 @@ import { } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { validateOrReject } from 'class-validator'; -import { metadataApplier } from './metadata-applier'; -import { UUIDValidate } from './validation-types'; export const ParameterUuid = ( parameterName: string, @@ -14,18 +13,23 @@ export const ParameterUuid = ( ) => createParamDecorator( async (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.params[data]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(UUIDValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException('Parameter is not a valid UUID!'); }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return value; }, metadataApplier( parameterName, - parameterDescription || 'UUID', + parameterDescription ?? 'UUID', 'path', 'string', true, diff --git a/backend/src/validation/query-decorators.ts b/backend/src/validation/query-decorators.ts index 3fb5b15f4..4394005e9 100644 --- a/backend/src/validation/query-decorators.ts +++ b/backend/src/validation/query-decorators.ts @@ -1,21 +1,21 @@ -import { AccessGroupType } from '@common/frontend_shared/enum'; -import { - BadRequestException, - createParamDecorator, - ExecutionContext, -} from '@nestjs/common'; -import { plainToInstance } from 'class-transformer'; -import { validateOrReject } from 'class-validator'; -import { metadataApplier } from './metadata-applier'; +import { AccessGroupType } from '@kleinkram/shared'; import { BooleanValidate, DateStringValidate, + metadataApplier, NumberValidate, StringArrayValidate, StringValidate, UUIDArrayValidate, UUIDValidate, -} from './validation-types'; +} from '@kleinkram/validation'; +import { + BadRequestException, + createParamDecorator, + ExecutionContext, +} from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { validateOrReject } from 'class-validator'; export const QueryUUID = ( parameterName: string, @@ -23,8 +23,12 @@ export const QueryUUID = ( ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(UUIDValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -32,7 +36,7 @@ export const QueryUUID = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, @@ -51,13 +55,17 @@ export const QueryOptionalUUID = ( createParamDecorator( // TODO: what type should this be? string | undefined or string async (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { return; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(UUIDValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -65,7 +73,7 @@ export const QueryOptionalUUID = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, @@ -82,9 +90,13 @@ export const QueryString = ( ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(StringValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -92,7 +104,7 @@ export const QueryString = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, @@ -109,13 +121,17 @@ export const QueryOptionalString = ( ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined || value === '') { return ''; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(StringValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -123,7 +139,7 @@ export const QueryOptionalString = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, @@ -140,7 +156,10 @@ export const QueryStringArray = ( ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const raw_value = request.query[data]; if (raw_value === undefined) { @@ -149,6 +168,7 @@ export const QueryStringArray = ( let value = []; try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument value = JSON.parse(raw_value); } catch { throw new BadRequestException( @@ -156,6 +176,7 @@ export const QueryStringArray = ( ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(StringArrayValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -163,7 +184,7 @@ export const QueryStringArray = ( ); }); - return value; + return value as string[]; }, metadataApplier( parameterName, @@ -180,7 +201,10 @@ export const QueryOptionalStringArray = ( ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const raw_value = request.query[data]; if (raw_value === undefined) { @@ -189,6 +213,7 @@ export const QueryOptionalStringArray = ( let value = []; try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument value = JSON.parse(raw_value); } catch { throw new BadRequestException( @@ -196,6 +221,7 @@ export const QueryOptionalStringArray = ( ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(StringArrayValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -203,7 +229,7 @@ export const QueryOptionalStringArray = ( ); }); - return value; + return value as string[]; }, metadataApplier( parameterName, @@ -220,7 +246,10 @@ export const QueryOptionalUUIDArray = ( ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const raw_value = request.query[data]; if (raw_value === undefined) { @@ -229,6 +258,7 @@ export const QueryOptionalUUIDArray = ( let value = []; try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument value = JSON.parse(raw_value); } catch { throw new BadRequestException( @@ -236,6 +266,7 @@ export const QueryOptionalUUIDArray = ( ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(UUIDArrayValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -243,7 +274,7 @@ export const QueryOptionalUUIDArray = ( ); }); - return value; + return value as string[]; }, metadataApplier( parameterName, @@ -260,7 +291,10 @@ export const QueryUUIDArray = ( ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const raw_value = request.query[data]; if (raw_value === undefined) { @@ -269,6 +303,7 @@ export const QueryUUIDArray = ( let value = []; try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument value = JSON.parse(raw_value); } catch { throw new BadRequestException( @@ -276,6 +311,7 @@ export const QueryUUIDArray = ( ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(UUIDArrayValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -283,7 +319,7 @@ export const QueryUUIDArray = ( ); }); - return value; + return value as string[]; }, metadataApplier( parameterName, @@ -300,7 +336,10 @@ export const QueryBoolean = ( ) => createParamDecorator( (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; // Handle string "true" or "false" conversion first @@ -319,6 +358,7 @@ export const QueryBoolean = ( // throw an error or attempt validation with the original value if (booleanValue === undefined) { throw new BadRequestException( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Parameter ${data} is not a valid Boolean. Received: "${value}"`, ); } @@ -341,11 +381,15 @@ export const QueryOptionalBoolean = ( ) => createParamDecorator( async (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { return; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(BooleanValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -379,13 +423,17 @@ export const QueryDate = ( ) => createParamDecorator( async (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { return new Date(0); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(DateStringValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -393,7 +441,7 @@ export const QueryDate = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, @@ -410,7 +458,10 @@ export const QuerySortBy = ( ) => createParamDecorator( async (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === '' || value === undefined) { @@ -437,10 +488,12 @@ export const QuerySortBy = ( 'state_cause', ]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument if (!fields.includes(value)) { throw new BadRequestException('Parameter is not a valid field'); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(StringValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -448,11 +501,11 @@ export const QuerySortBy = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, - parameterDescription || 'Sort response by', + parameterDescription ?? 'Sort response by', 'query', 'string (sortable field)', true, @@ -465,13 +518,17 @@ export const QuerySortDirection = ( ) => createParamDecorator( async (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access let value = request.query[data]; if (value === '' || value === undefined) { return 'ASC'; // default value } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access value = value.toUpperCase(); if (value !== 'ASC' && value !== 'DESC') { throw new BadRequestException( @@ -479,6 +536,7 @@ export const QuerySortDirection = ( ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(StringValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -486,11 +544,11 @@ export const QuerySortDirection = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, - parameterDescription || 'Sort response direction', + parameterDescription ?? 'Sort response direction', 'query', 'boolean', false, @@ -503,7 +561,10 @@ export const QueryProjectSearchParameters = ( ) => createParamDecorator( (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { @@ -512,21 +573,28 @@ export const QueryProjectSearchParameters = ( // check if it is a valid key const validKeys = ['name', 'creator.uuid']; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const key = Object.keys(value)[0] ?? ''; if (!validKeys.includes(key)) { throw new BadRequestException('Parameter is not a valid key'); } // remove empty values + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument for (const _key of Object.keys(value)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (value[_key] === '') { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete, @typescript-eslint/no-unsafe-member-access delete value[_key]; } } // check if every value is a string + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument for (const _key of Object.keys(value)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (typeof value[_key] !== 'string') { throw new BadRequestException( 'Parameter is not a valid value', @@ -534,7 +602,7 @@ export const QueryProjectSearchParameters = ( } } - return value; + return value as Record; }, metadataApplier( parameterName, @@ -551,12 +619,16 @@ export const QueryOptionalDate = ( ) => createParamDecorator( async (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { return; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(DateStringValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -564,7 +636,7 @@ export const QueryOptionalDate = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, @@ -581,13 +653,17 @@ export const QueryOptionalNumber = ( ) => createParamDecorator( async (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { return; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const object = plainToInstance(NumberValidate, { value }); await validateOrReject(object).catch(() => { throw new BadRequestException( @@ -595,7 +671,7 @@ export const QueryOptionalNumber = ( ); }); - return value; + return value as string; }, metadataApplier( parameterName, @@ -615,7 +691,10 @@ export const QueryOptionalAccessGroupType = ( data: string, context: ExecutionContext, ): AccessGroupType | undefined => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { @@ -623,6 +702,7 @@ export const QueryOptionalAccessGroupType = ( } // validate if value is a valid AccessGroupType + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument if (!Object.values(AccessGroupType).includes(value)) { throw new BadRequestException( 'Parameter is not a valid AccessGroupType', @@ -646,7 +726,10 @@ export const QuerySkip = ( ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-member-access const value_raw = request.query[data] as string | undefined; if (value_raw === undefined) { @@ -677,7 +760,7 @@ export const QuerySkip = ( }, metadataApplier( parameterName, - parameterDescription || 'Pagination Skip', + parameterDescription ?? 'Pagination Skip', 'query', 'number', false, @@ -689,11 +772,15 @@ const DEFAULT_TAKE = 100; export const QueryTake = ( parameterName: string, + parameterDescription?: string, ) => createParamDecorator( async (data: string, context: ExecutionContext): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-member-access const raw_value = request.query[data] as string | undefined; if (raw_value === undefined) { @@ -726,7 +813,7 @@ export const QueryTake = ( }, metadataApplier( parameterName, - parameterDescription || 'Pagination Take', + parameterDescription ?? 'Pagination Take', 'query', 'number', false, @@ -739,14 +826,17 @@ export const QueryOptional = ( ) => createParamDecorator( (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { return; } - return value; + return value as string; }, metadataApplier( parameterName, @@ -763,13 +853,17 @@ export const QueryOptionalRecord = ( ) => createParamDecorator( (data: string, context: ExecutionContext) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const request = context.switchToHttp().getRequest(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const value = request.query[data]; if (value === undefined) { return; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access return JSON.parse(value.replaceAll("'", '"')); }, metadataApplier( diff --git a/backend/src/validation/validation-logic.ts b/backend/src/validation/validation-logic.ts deleted file mode 100644 index b3c0d234d..000000000 --- a/backend/src/validation/validation-logic.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FileType } from '@common/frontend_shared/enum'; - -export const MISSION_NAME_REGEX = /^[\w\-_]{3,50}$/; - -export const PROJECT_NAME_REGEX = /^[\w\-_]{3,50}$/; - -const validTypes = Object.values(FileType).filter( - (type) => type !== FileType.ALL, -); -const extensionsGroup = validTypes.map((type) => type.toLowerCase()).join('|'); -const regexString = `^[\\w\\-.()]{3,50}\\.(${extensionsGroup})$`; -export const FILE_NAME_REGEX = new RegExp(regexString); - -export const NON_UUID_REGEX = - /^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)/; diff --git a/backend/tests/actions/action-access-rights.test.ts b/backend/tests/actions/action-access-rights.test.ts new file mode 100644 index 000000000..69f4e3d7f --- /dev/null +++ b/backend/tests/actions/action-access-rights.test.ts @@ -0,0 +1,130 @@ +import { CreateTemplateDto } from '@kleinkram/api-dto/types/actions/create-template.dto'; +import { SubmitActionDto } from '@kleinkram/api-dto/types/submit-action-response.dto'; +import { AccessGroupRights } from '@kleinkram/shared'; +import { DEFAULT_URL, generateAndFetchDatabaseUser } from '../auth/utilities'; +import { + createMissionUsingPost, + createProjectUsingPost, + HeaderCreator, +} from '../utils/api-calls'; +import { clearAllData, database } from '../utils/database-utilities'; + +describe('Action Access Rights', () => { + beforeAll(async () => { + await database.initialize(); + await clearAllData(); + }); + + afterAll(async () => { + await database.destroy(); + }); + + test('if a user can only start an action if they have sufficient rights', async () => { + // 1. Create a user (creator) + const { user: creator } = await generateAndFetchDatabaseUser( + 'internal', + 'user', + ); + + // 2. Create a project and mission + const projectUUID = await createProjectUsingPost( + { + name: 'access_rights_project', + description: 'Test project for access rights', + }, + creator, + ); + expect(projectUUID).toBeDefined(); + + const missionUUID = await createMissionUsingPost( + { + name: 'access_rights_mission', + projectUUID: projectUUID, + tags: {}, + ignoreTags: false, + }, + creator, + ); + expect(missionUUID).toBeDefined(); + + // 3. Create another user (limitedUser) + const { user: limitedUser } = await generateAndFetchDatabaseUser( + 'external', + 'user', + ); + + // 4. Create Action Template requiring WRITE rights + const writeHeaders = new HeaderCreator(creator); + writeHeaders.addHeader('Content-Type', 'application/json'); + const writeTemplateResponse = await fetch(`${DEFAULT_URL}/templates`, { + method: 'POST', + headers: writeHeaders.getHeaders(), + body: JSON.stringify({ + name: 'write_access_template', + description: 'Test template', + dockerImage: 'hello-world', + cpuCores: 1, + cpuMemory: 512, + gpuMemory: 0, + maxRuntime: 60, + accessRights: AccessGroupRights.WRITE, + } as CreateTemplateDto), + }); + if (writeTemplateResponse.status !== 201) { + console.log( + '[DEBUG] Template creation error:', + await writeTemplateResponse.text(), + ); + } + expect(writeTemplateResponse.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { uuid: writeTemplateUUID } = await writeTemplateResponse.json(); + + // 5. Create Action Template requiring READ rights + const readHeaders = new HeaderCreator(creator); + readHeaders.addHeader('Content-Type', 'application/json'); + const readTemplateResponse = await fetch(`${DEFAULT_URL}/templates`, { + method: 'POST', + headers: readHeaders.getHeaders(), + body: JSON.stringify({ + name: 'read_access_template', + description: 'Test template', + dockerImage: 'hello-world', + cpuCores: 1, + cpuMemory: 512, + gpuMemory: 0, + maxRuntime: 60, + accessRights: AccessGroupRights.READ, + } as CreateTemplateDto), + }); + expect(readTemplateResponse.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { uuid: readTemplateUUID } = await readTemplateResponse.json(); + + // 6. Try to submit WRITE action as limitedUser (should fail 403) + const limitedHeaders = new HeaderCreator(limitedUser); + limitedHeaders.addHeader('Content-Type', 'application/json'); + const failResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: limitedHeaders.getHeaders(), + body: JSON.stringify({ + missionUUID: missionUUID, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + templateUUID: writeTemplateUUID, + } as SubmitActionDto), + }); + expect(failResponse.status).toBe(403); + + // 7. Try to submit READ action as limitedUser (should also fail 403 if they have NO rights) + const failReadResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: limitedHeaders.getHeaders(), + body: JSON.stringify({ + missionUUID: missionUUID, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + templateUUID: readTemplateUUID, + } as SubmitActionDto), + }); + expect(failReadResponse.status).toBe(403); + }); +}); diff --git a/backend/tests/actions/action-crud.test.ts b/backend/tests/actions/action-crud.test.ts new file mode 100644 index 000000000..0a1e53640 --- /dev/null +++ b/backend/tests/actions/action-crud.test.ts @@ -0,0 +1,106 @@ +import { AccessGroupRights } from '@kleinkram/shared'; +import { database } from '../utils/database-utilities'; + +import { createActionUsingPost, getAuthHeaders } from '../utils/api-calls'; + +import { ActionTemplateEntity } from '@kleinkram/backend-common'; +import { DEFAULT_URL } from '../auth/utilities'; +import { + createMockWorker, + setupDatabaseHooks, + setupTestEnvironment, +} from '../utils/test-helpers'; + +describe('Action Management Tests', () => { + setupDatabaseHooks(); + + test('should create and archive an action template', async () => { + const { user } = await setupTestEnvironment( + 'test-action@kleinkram.dev', + 'Action User', + ); + + // Create Action Template + const templateUuid = await createActionUsingPost( + { + name: 'Archive Test Action', + description: 'desc', + accessRights: AccessGroupRights.READ, + dockerImage: 'hello-world', + maxRuntime: 10, + cpuCores: 1, + cpuMemory: 2, + gpuMemory: 0, + }, + user, + ); + + const templateRepo = database.getRepository(ActionTemplateEntity); + const template = await templateRepo.findOneOrFail({ + where: { uuid: templateUuid }, + }); + expect(template.isArchived).toBe(false); + + // Archive Action Template + const deleteResponse = await fetch( + `${DEFAULT_URL}/templates/${templateUuid}`, + { + method: 'DELETE', + headers: getAuthHeaders(user), + }, + ); + expect(deleteResponse.status).toBeLessThan(300); + + // Verify it is deleted (hard delete) + try { + await templateRepo.findOneOrFail({ + where: { uuid: templateUuid }, + }); + fail('Template should have been deleted'); + } catch (error) { + expect(error).toBeDefined(); + } + }, 30_000); + + test('should submit an action run', async () => { + const { user, missionUuid } = await setupTestEnvironment( + 'test-run@kleinkram.io', + 'Run User', + ); + + const templateUuid = await createActionUsingPost( + { + name: 'Run Test Action', + description: 'desc', + accessRights: AccessGroupRights.READ, + dockerImage: 'hello-world', + maxRuntime: 10, + cpuCores: 1, + cpuMemory: 2, + gpuMemory: 0, + }, + user, + ); + + // Create Worker + await createMockWorker('test-worker-run'); + + // Submit action + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const submitResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Content-Type': 'application/json', + ...getAuthHeaders(user), + }, + body: JSON.stringify({ + missionUUID: missionUuid, + templateUUID: templateUuid, + }), + }); + // Expect 409 because no worker is available (unless we mock it like in action-file-events) + // or 201 if we don't care about execution success but just submission. + }, 30_000); +}); diff --git a/backend/tests/actions/action-file-events.test.ts b/backend/tests/actions/action-file-events.test.ts new file mode 100644 index 000000000..cb8905982 --- /dev/null +++ b/backend/tests/actions/action-file-events.test.ts @@ -0,0 +1,212 @@ +import { FileEventsDto } from '@kleinkram/api-dto/types/file/file-event.dto'; +import { + ActionEntity, + ActionTemplateEntity, + ApiKeyEntity, + FileEntity, + MissionEntity, + UserEntity, +} from '@kleinkram/backend-common'; + +import { appVersion } from '@/app-version'; +import environment from '@kleinkram/backend-common/environment'; +import * as Minio from 'minio'; +import { DEFAULT_URL, generateAndFetchDatabaseUser } from '../auth/utilities'; +import { + createActionUsingPost, + createMissionUsingPost, + createProjectUsingPost, + getAuthHeaders, +} from '../utils/api-calls'; + +import { + AccessGroupRights, + ActionState, + ArtifactState, + CookieNames, + FileEventType, + FileState, + FileType, + KeyTypes, +} from '@kleinkram/shared'; +import { database } from '../utils/database-utilities'; +import { setupDatabaseHooks } from '../utils/test-helpers'; + +describe('Action File Events', () => { + setupDatabaseHooks(); + + test('should track file events triggered by an action', async () => { + console.log('DEFAULT_URL:', DEFAULT_URL); + // 1. Setup User + const setup = await generateAndFetchDatabaseUser('internal', 'user'); + const user: UserEntity = setup.user; + + // 2. Create Mission + const missionResponse = await createMissionUsingPost( + { + name: 'test_mission', + projectUUID: await createProjectUsingPost( + { + name: 'test_project', + description: 'desc', + requiredTags: [], + accessGroups: [], + }, + user, + ), + tags: {}, + ignoreTags: true, + }, + user, + ); + const missionUuid = missionResponse; + + // Create a file + const fileRepo = database.getRepository(FileEntity); + const missionRepo = database.getRepository(MissionEntity); + const mission = await missionRepo.findOneByOrFail({ + uuid: missionUuid, + }); + + const file = await fileRepo.save( + fileRepo.create({ + filename: 'test.bag', + mission: mission, + // project: mission.project, // project is not a direct property of FileEntity + creator: user, + type: FileType.BAG, + size: 123, + date: new Date(), + state: FileState.OK, + }), + ); + + // 3. Create Action Template + const createdTemplate = await createActionUsingPost( + { + name: 'Test Action', + description: 'desc', + accessRights: AccessGroupRights.WRITE, // Needs write access to update file + dockerImage: 'hello-world', + maxRuntime: 10, + cpuCores: 1, + cpuMemory: 2, + gpuMemory: 0, + }, + user, + ); + const templateUuid = createdTemplate; + + // 4. Manually Create Action (Mock Execution) + const actionRepo = database.getRepository(ActionEntity); + const templateRepo = database.getRepository(ActionTemplateEntity); + const template = await templateRepo.findOneOrFail({ + where: { uuid: templateUuid }, + }); + + const action = actionRepo.create({ + state: ActionState.PROCESSING, + template: template, + mission: mission, + creator: user, + artifacts: ArtifactState.AWAITING_ACTION, + image: { + sha: 'sha256:mock', + repoDigests: [], + }, + }); + await actionRepo.save(action); + const actionUUID = action.uuid; + + // 5. Create Action API Key + const apikeyRepo = database.getRepository(ApiKeyEntity); + const apikeyEntity = apikeyRepo.create({ + // eslint-disable-next-line @typescript-eslint/naming-convention + key_type: KeyTypes.ACTION, + mission: mission, + action: action, + rights: AccessGroupRights.WRITE, + user: user, + }); + await apikeyRepo.save(apikeyEntity); + const actionKey = apikeyEntity.apikey; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const apiKey = actionKey; + + // 4. Create a File directly in DB + // Reuse the file created earlier + // const missionRepo = database.getRepository(MissionEntity); + // const mission = await missionRepo.findOneByOrFail({ + // uuid: missionUuid, + // }); + + // const fileRepo = database.getRepository(FileEntity); + // const file = fileRepo.create({ + // filename: 'test.bag', + // mission: mission, + // size: 1024, + // // bucket: 'data', // bucket does not exist on FileEntity + // hash: 'hash', + // state: FileState.OK, + // date: new Date(), + // type: FileType.BAG, + // creator: user, + // createdAt: new Date(), + // updatedAt: new Date(), + // }); + // await fileRepo.save(file); + + // Upload file to MinIO to avoid 500 error during update (which tries to tag the object) + const minioClient = new Minio.Client({ + endPoint: environment.MINIO_ENDPOINT || 'localhost', + port: 9000, + useSSL: false, + accessKey: environment.MINIO_ACCESS_KEY, + secretKey: environment.MINIO_SECRET_KEY, + }); + + const bucketName = environment.MINIO_DATA_BUCKET_NAME; + // Bucket is created on container start + await minioClient.putObject( + bucketName, + file.uuid, + Buffer.from('dummy content'), + ); + + // 5. Download File using Action API Key + const downloadResponse = await fetch( + `${DEFAULT_URL}/files/download?uuid=${file.uuid}&expires=false&preview_only=false`, + { + method: 'GET', + headers: { + cookie: `${CookieNames.CLI_KEY}=${actionKey}`, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kleinkram-client-version': appVersion, + }, + }, + ); + expect(downloadResponse.status).toBe(200); + + // 6. Verify Download Event via Action Endpoint + const eventsResponse = await fetch( + `${DEFAULT_URL}/actions/${actionUUID}/file-events`, + { + method: 'GET', + headers: getAuthHeaders(user), + }, + ); + expect(eventsResponse.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const events: FileEventsDto = await eventsResponse.json(); + + expect(events.count).toBeGreaterThan(0); + + const downloadEvent = events.data.find( + // eslint-disable-next-line unicorn/prevent-abbreviations + (e) => e.type === FileEventType.DOWNLOADED, + ); + expect(downloadEvent).toBeDefined(); + expect(downloadEvent?.action?.uuid).toBe(actionUUID); + expect(downloadEvent?.action?.name).toBe('Test Action'); + }, 60_000); +}); diff --git a/backend/tests/actions/api-key-rights.test.ts b/backend/tests/actions/api-key-rights.test.ts new file mode 100644 index 000000000..43e6dc6f0 --- /dev/null +++ b/backend/tests/actions/api-key-rights.test.ts @@ -0,0 +1,255 @@ +// eslint-disable-next-line @typescript-eslint/triple-slash-reference +/// +import { CreateTemplateDto } from '@kleinkram/api-dto/types/actions/create-template.dto'; +import { + AccessGroupEntity, + ActionEntity, + ActionTemplateEntity, + ApiKeyEntity, + MissionEntity, + ProjectEntity, + UserEntity, + WorkerEntity, +} from '@kleinkram/backend-common'; + +import { AccessGroupRights, ActionState, KeyTypes } from '@kleinkram/shared'; + +import { DEFAULT_URL, generateAndFetchDatabaseUser } from '../auth/utilities'; +import { + createMissionUsingPost, + createProjectUsingPost, + HeaderCreator, +} from '../utils/api-calls'; +import { clearAllData, database } from '../utils/database-utilities'; + +const createTemplateViaApi = async ( + user: UserEntity, + dto: CreateTemplateDto, +) => { + const headersBuilder = new HeaderCreator(user); + headersBuilder.addHeader('Content-Type', 'application/json'); + + const response = await fetch(`${DEFAULT_URL}/templates`, { + method: 'POST', + headers: headersBuilder.getHeaders(), + body: JSON.stringify(dto), + }); + + if (response.status >= 300) { + throw new Error( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Failed to create template: ${response.status} ${response.statusText}`, + ); + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return response.json(); +}; + +describe('Verify Action Access Rights', () => { + beforeAll(async () => { + await database.initialize(); + await clearAllData(); + + // Create internal user (Creator) + ({ + user: globalThis.creator, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + token: globalThis.creator.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + response: globalThis.creator.Response, + } = await generateAndFetchDatabaseUser('internal', 'user')); + }); + + beforeEach(async () => { + // 1. Setup Access Groups + const accessGroupRepository = + database.getRepository(AccessGroupEntity); + const accessGroupCreator = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + where: { name: globalThis.creator.name }, + }); + + // 2. Generate Project + globalThis.projectUuid = await createProjectUsingPost( + { + name: 'rights_test_project', + description: 'Project for rights testing', + requiredTags: [], + accessGroups: [ + { + rights: AccessGroupRights.DELETE, // User has full rights + accessGroupUUID: accessGroupCreator.uuid, + }, + ], + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + ); + + // 3. Create Template + const templateDto: CreateTemplateDto = { + name: 'rights_test_template', + description: 'Template for rights testing', + // projectUUID removed as it is not in DTO + dockerImage: 'rslethz/test', + cpuCores: 1, + cpuMemory: 1, + gpuMemory: -1, + maxRuntime: 1, + accessRights: AccessGroupRights.READ, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const createdTemplate = await createTemplateViaApi( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + templateDto, + ); + console.log('DEBUG: createdTemplate', createdTemplate); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + globalThis.templateUuid = createdTemplate.uuid; + + // 4. Create Mission + globalThis.missionUuid = await createMissionUsingPost( + { + name: 'rights_test_mission', + projectUUID: globalThis.projectUuid, + tags: {}, + ignoreTags: true, + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + ); + }); + + afterEach(async () => { + const repos = [ + { name: 'Actions', entity: ActionEntity }, + { name: 'Templates', entity: ActionTemplateEntity }, + { name: 'Missions', entity: MissionEntity }, + { name: 'Projects', entity: ProjectEntity }, + { name: 'ApiKeys', entity: ApiKeyEntity }, + { name: 'Workers', entity: WorkerEntity }, + ]; + + for (const repo of repos) { + const repository = database.getRepository(repo.entity); + const items = await repository.find(); + if (items.length > 0) { + await repository.remove(items); + } + } + }); + + afterAll(async () => { + if (database.isInitialized) { + await database.destroy(); + } + }); + + test('if an action with READ rights CANNOT delete a mission', async () => { + // 1. Manually Create Action and API Key + const missionRepo = database.getRepository(MissionEntity); + const mission = await missionRepo.findOneOrFail({ + where: { uuid: globalThis.missionUuid }, + }); + + const templateRepo = database.getRepository(ActionTemplateEntity); + const template = await templateRepo.findOneOrFail({ + where: { uuid: globalThis.templateUuid }, + }); + + const actionRepo = database.getRepository(ActionEntity); + const action = actionRepo.create({ + mission: mission, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + creator: globalThis.creator, + state: ActionState.PROCESSING, + template: template, + }); + await actionRepo.save(action); + + const apiKeyRepo = database.getRepository(ApiKeyEntity); + const apiKeyEntity = apiKeyRepo.create({ + // eslint-disable-next-line @typescript-eslint/naming-convention + key_type: KeyTypes.ACTION, + mission: mission, + action: action, + rights: AccessGroupRights.READ, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + user: globalThis.creator, + }); + await apiKeyRepo.save(apiKeyEntity); + const apiKey = apiKeyEntity.apikey; + + // 2. Try to DELETE the mission using the Action API Key + const deleteResponse = await fetch( + `${DEFAULT_URL}/mission/${globalThis.missionUuid}`, + { + method: 'DELETE', + + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'x-api-key': apiKey, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kleinkram-client-version': '0.56.0', + }, + }, + ); + + // Should be Forbidden because action only has READ rights + expect(deleteResponse.status).toBe(403); + }); + + test('if an action with READ rights CAN read a mission', async () => { + // 1. Manually Create Action and API Key + const missionRepo = database.getRepository(MissionEntity); + const mission = await missionRepo.findOneOrFail({ + where: { uuid: globalThis.missionUuid }, + }); + + const templateRepo = database.getRepository(ActionTemplateEntity); + const template = await templateRepo.findOneOrFail({ + where: { uuid: globalThis.templateUuid }, + }); + + const actionRepo = database.getRepository(ActionEntity); + const action = actionRepo.create({ + mission: mission, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + creator: globalThis.creator, + state: ActionState.PROCESSING, + template: template, + }); + await actionRepo.save(action); + + const apiKeyRepo = database.getRepository(ApiKeyEntity); + const apiKeyEntity = apiKeyRepo.create({ + // eslint-disable-next-line @typescript-eslint/naming-convention + key_type: KeyTypes.ACTION, + mission: mission, + action: action, + rights: AccessGroupRights.READ, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + user: globalThis.creator, + }); + await apiKeyRepo.save(apiKeyEntity); + const apiKey = apiKeyEntity.apikey; + + // 2. Try to GET the mission using the Action API Key + const getResponse = await fetch( + `${DEFAULT_URL}/mission/one?uuid=${globalThis.missionUuid}`, + + { + method: 'GET', + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'x-api-key': apiKey, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kleinkram-client-version': '0.56.0', + }, + }, + ); + + expect(getResponse.status).toBe(200); + }); +}); diff --git a/backend/tests/actions/api-key-scope.test.ts b/backend/tests/actions/api-key-scope.test.ts new file mode 100644 index 000000000..6a5b11581 --- /dev/null +++ b/backend/tests/actions/api-key-scope.test.ts @@ -0,0 +1,298 @@ +// eslint-disable-next-line @typescript-eslint/triple-slash-reference +/// +import { CreateTemplateDto } from '@kleinkram/api-dto/types/actions/create-template.dto'; +import { SubmitActionDto } from '@kleinkram/api-dto/types/submit-action-response.dto'; +import { + AccessGroupEntity, + ActionEntity, + ActionTemplateEntity, + ApiKeyEntity, + MissionEntity, + ProjectEntity, + UserEntity, + WorkerEntity, +} from '@kleinkram/backend-common'; +import { AccessGroupRights, KeyTypes } from '@kleinkram/shared'; +import { DEFAULT_URL, generateAndFetchDatabaseUser } from '../auth/utilities'; +import { + createMissionUsingPost, + createProjectUsingPost, + HeaderCreator, +} from '../utils/api-calls'; +import { clearAllData, database } from '../utils/database-utilities'; + +describe('Verify Action API Key Scope', () => { + const createTemplateViaApi = async ( + user: UserEntity, + dto: CreateTemplateDto, + // eslint-disable-next-line unicorn/consistent-function-scoping + ) => { + const headersBuilder = new HeaderCreator(user); + headersBuilder.addHeader('Content-Type', 'application/json'); + + const response = await fetch(`${DEFAULT_URL}/templates`, { + method: 'POST', + headers: headersBuilder.getHeaders(), + body: JSON.stringify(dto), + }); + + if (response.status >= 300) { + throw new Error( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Failed to create template: ${response.status} ${response.statusText}`, + ); + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return response.json(); + }; + + beforeAll(async () => { + await database.initialize(); + await clearAllData(); + + // Create internal user (Creator) + ({ + user: globalThis.creator, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + token: globalThis.creator.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + response: globalThis.creator.Response, + } = await generateAndFetchDatabaseUser('internal', 'user')); + }); + + beforeEach(async () => { + // 1. Setup Access Groups + const accessGroupRepository = + database.getRepository(AccessGroupEntity); + const accessGroupCreator = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + where: { name: globalThis.creator.name }, + }); + + // 2. Generate Project + globalThis.projectUuid = await createProjectUsingPost( + { + name: 'scope_test_project', + description: 'Project for scope testing', + requiredTags: [], + accessGroups: [ + { + rights: AccessGroupRights.DELETE, + accessGroupUUID: accessGroupCreator.uuid, + }, + ], + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + ); + + // 3. Create Mission + globalThis.missionUuid = await createMissionUsingPost( + { + name: 'scope_test_mission', + projectUUID: globalThis.projectUuid, + tags: {}, + ignoreTags: true, + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + ); + + // 4. Create Action Template + const templateDto: CreateTemplateDto = { + name: 'scope_test_template', + description: 'Template for scope testing', + command: '', + cpuCores: 1, + cpuMemory: 1, + entrypoint: 'd', + gpuMemory: -1, + dockerImage: 'rslethz/test', + accessRights: AccessGroupRights.READ, // Minimal rights + maxRuntime: 1, + }; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const createdTemplate = await createTemplateViaApi( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + templateDto, + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + globalThis.templateUuid = createdTemplate.uuid; + + // 5. Create a Worker + const workerRepo = database.getRepository(WorkerEntity); + const worker = workerRepo.create({ + identifier: 'test-worker-id', + hostname: 'test-host', + reachable: true, + lastSeen: new Date(), + cpuMemory: 1000, + cpuCores: 4, + cpuModel: 'test-cpu', + storage: 1000, + gpuMemory: -1, + }); + await workerRepo.save(worker); + + // Initialize queue for this worker (normally done by ActionDispatcherService.onModuleInit) + // The healthCheck cron runs every 30 seconds and will initialize queues for new workers + // We need to wait for it to run at least once + // await new Promise((resolve) => setTimeout(resolve, 35000)); + }, 40_000); // 40-second timeout for beforeEach + + afterEach(async () => { + const repos = [ + { name: 'Actions', entity: ActionEntity }, + { name: 'Templates', entity: ActionTemplateEntity }, + { name: 'Missions', entity: MissionEntity }, + { name: 'Projects', entity: ProjectEntity }, + { name: 'ApiKeys', entity: ApiKeyEntity }, + { name: 'Workers', entity: WorkerEntity }, + ]; + + for (const repo of repos) { + const repository = database.getRepository(repo.entity); + const items = await repository.find(); + if (items.length > 0) { + await repository.remove(items); + } + } + }); + + afterAll(async () => { + if (database.isInitialized) { + await database.destroy(); + } + }); + + test('if an action API key can access its own project', async () => { + // 1. Submit Action + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const headers = new HeaderCreator(globalThis.creator); + headers.addHeader('Content-Type', 'application/json'); + const submitResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: headers.getHeaders(), + body: JSON.stringify({ + missionUUID: globalThis.missionUuid, + templateUUID: globalThis.templateUuid, + } as SubmitActionDto), + }); + expect(submitResponse.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { actionUUID: uuid } = await submitResponse.json(); + + // 2. Get the API Key for the action (simulating worker retrieval or internal logic) + // Since we removed the automatic creation in the dispatcher, we need to manually create it here + // to simulate the worker creating it upon execution start. + const actionRepo = database.getRepository(ActionEntity); + const action = await actionRepo.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + where: { uuid }, + }); + + const apiKeyRepo = database.getRepository(ApiKeyEntity); + const apiKeyEntity = apiKeyRepo.create({ + // eslint-disable-next-line @typescript-eslint/naming-convention + key_type: KeyTypes.ACTION, + mission: { uuid: globalThis.missionUuid }, + action: action, + rights: AccessGroupRights.READ, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + user: globalThis.creator, + }); + await apiKeyRepo.save(apiKeyEntity); + const apiKey = apiKeyEntity.apikey; + + // 3. Use API Key to fetch project details + const projectResponse = await fetch( + `${DEFAULT_URL}/projects/${globalThis.projectUuid}`, + { + method: 'GET', + + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'x-api-key': apiKey, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kleinkram-client-version': '0.56.0', + }, + }, + ); + + expect(projectResponse.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const project = await projectResponse.json(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(project.uuid).toBe(globalThis.projectUuid); + }); + + test('if an action API key CANNOT access other projects', async () => { + // 1. Create another project (Alien Project) + const alienProjectUuid = await createProjectUsingPost( + { + name: 'alien_project', + description: 'Alien project', + requiredTags: [], + accessGroups: [], // No access for creator needed for this test specifically, but api requires it usually. + // Actually, let's just create it with creator so it exists. + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + ); + + // 2. Submit Action in the original project + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const headers = new HeaderCreator(globalThis.creator); + headers.addHeader('Content-Type', 'application/json'); + const submitResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: headers.getHeaders(), + body: JSON.stringify({ + missionUUID: globalThis.missionUuid, + templateUUID: globalThis.templateUuid, + } as SubmitActionDto), + }); + expect(submitResponse.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { actionUUID: uuid } = await submitResponse.json(); + + // 3. Get API Key + const actionRepo = database.getRepository(ActionEntity); + const action = await actionRepo.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + where: { uuid }, + }); + + const apiKeyRepo = database.getRepository(ApiKeyEntity); + const apiKeyEntity = apiKeyRepo.create({ + // eslint-disable-next-line @typescript-eslint/naming-convention + key_type: KeyTypes.ACTION, + mission: { uuid: globalThis.missionUuid }, + action: action, + rights: AccessGroupRights.READ, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + user: globalThis.creator, + }); + await apiKeyRepo.save(apiKeyEntity); + const apiKey = apiKeyEntity.apikey; + + // 4. Try to access Alien Project + const projectResponse = await fetch( + `${DEFAULT_URL}/projects/${alienProjectUuid}`, + + { + method: 'GET', + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'x-api-key': apiKey, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kleinkram-client-version': '0.56.0', + }, + }, + ); + + expect(projectResponse.status).toBe(403); // Forbidden + }); +}); diff --git a/backend/tests/actions/file-hash/Dockerfile b/backend/tests/actions/file-hash/Dockerfile index 7971bb18d..a719462ae 100644 --- a/backend/tests/actions/file-hash/Dockerfile +++ b/backend/tests/actions/file-hash/Dockerfile @@ -11,4 +11,4 @@ COPY ./backend/tests/actions/file-hash/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh # set entrypoint -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["/entrypoint.sh"] diff --git a/backend/tests/actions/test-action/Dockerfile b/backend/tests/actions/test-action/Dockerfile index 259bf8c3b..7ba2a8948 100644 --- a/backend/tests/actions/test-action/Dockerfile +++ b/backend/tests/actions/test-action/Dockerfile @@ -11,4 +11,4 @@ COPY ./backend/tests/actions/test-action/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh # set entrypoint -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["/entrypoint.sh"] diff --git a/backend/tests/actions/test-actions.disabled-test.ts b/backend/tests/actions/test-actions.disabled-test.ts deleted file mode 100644 index 89fff33c5..000000000 --- a/backend/tests/actions/test-actions.disabled-test.ts +++ /dev/null @@ -1,457 +0,0 @@ -import UserEntity from '@common/entities/user/user.entity'; -import { clearAllData, database } from '../utils/database-utilities'; - -import { SubmitActionDto } from '@common/api/types/submit-action-response.dto'; -import ActionTemplateEntity from '@common/entities/action/action-template.entity'; -import ActionEntity from '@common/entities/action/action.entity'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import { AccessGroupRights } from '@common/frontend_shared/enum'; -import { DEFAULT_URL, generateAndFetchDatabaseUser } from '../auth/utilities'; -import { - createActionUsingPost, - createMissionUsingPost, - createProjectUsingPost, - HeaderCreator, -} from '../utils/api-calls'; - -describe('Verify Action', () => { - beforeAll(async () => { - await database.initialize(); - await clearAllData(); - - // global url set in utilities - console.log(`[DEBUG]: Global url: ${DEFAULT_URL}`); - - // Create internal user - ({ - user: globalThis.creator as UserEntity, - token: globalThis.creator.token, - response: globalThis.creator.Response, - } = await generateAndFetchDatabaseUser('internal', 'user')); - console.log(`[DEBUG]: Global creator: ${globalThis.creator.name}`); - - // Create 2nd internal user - ({ - user: globalThis.user as UserEntity, - token: globalThis.userToken, - response: globalThis.userResponse, - } = await generateAndFetchDatabaseUser('internal', 'user')); - console.log(`[DEBUG]: Global user: ${globalThis.user.name}`); - - // Create external user - ({ - user: globalThis.externalUser as UserEntity, - token: globalThis.externalUser.token, - response: globalThis.externalUser.response, - } = await generateAndFetchDatabaseUser('external', 'user')); - console.log( - `[DEBUG]: Global external user: ${globalThis.externalUser.name}`, - ); - - // Create admin user - ({ - user: globalThis.admin as UserEntity, - token: globalThis.admin.token, - response: globalThis.admin.response, - } = await generateAndFetchDatabaseUser('internal', 'admin')); - console.log(`[DEBUG]: Global admin: ${globalThis.admin.name}`); - }); - - beforeEach(async () => { - // get access group for creator and user - const accessGroupRepository = - database.getRepository(AccessGroupEntity); - const accessGroupCreator = await accessGroupRepository.findOneOrFail({ - where: { name: globalThis.creator.name }, - }); - const accessGroupUser = await accessGroupRepository.findOneOrFail({ - where: { name: globalThis.user.name }, - }); - // generate project with creator - globalThis.projectUuid = await createProjectUsingPost( - { - name: 'test_project', - description: 'This is a test project', - requiredTags: [], - accessGroups: [ - { - rights: AccessGroupRights.DELETE, - accessGroupUUID: accessGroupCreator.uuid, - }, - { - rights: AccessGroupRights.CREATE, - accessGroupUUID: accessGroupUser.uuid, - }, - ], - }, - globalThis.creator, - ); - - // check if project is created - const projectRepository = - database.getRepository(ProjectEntity); - const project = await projectRepository.findOneOrFail({ - where: { uuid: globalThis.projectUuid }, - }); - - expect(project.name).toBe('test_project'); - expect(project.description).toBe('This is a test project'); - expect(project.uuid).toBe(globalThis.projectUuid); - - // create a mission - globalThis.missionUuid = await createMissionUsingPost( - { - name: 'test_mission', - projectUUID: globalThis.projectUuid, - tags: {}, - ignoreTags: true, - }, - globalThis.creator, - ); - - // check if mission is generated - const missionRepository = - database.getRepository(MissionEntity); - const mission = await missionRepository.findOneOrFail({ - where: { uuid: globalThis.missionUuid }, - }); - expect(mission['name']).toBe('test_mission'); - const missions = await missionRepository.find(); - expect(missions.length).toBe(1); - console.log( - `[DEBUG]: Mission created with UUID: ${globalThis.missionUuid}`, - ); - - // create action - globalThis.actionUuid = await createActionUsingPost( - { - name: 'test_action', - command: '', - cpuCores: 2, - cpuMemory: 2, - entrypoint: 'd', - gpuMemory: -1, - dockerImage: 'rslethz/test', - accessRights: AccessGroupRights.DELETE, - maxRuntime: 1, - searchable: false, - }, - globalThis.creator, - ); - // check if action is generated - const actionRepository = - database.getRepository(ActionTemplateEntity); - const action = await actionRepository.findOneOrFail({ - where: { uuid: globalThis.actionUuid }, - }); - expect(action['name']).toBe('test_action'); - const actions = await actionRepository.find(); - expect(actions.length).toBe(1); - console.log( - `[DEBUG]: Action created with UUID: ${globalThis.actionUuid}`, - ); - }); - - afterEach(async () => { - // check if users are still in the database - const userRepository = database.getRepository(UserEntity); - const users = await userRepository.find(); - expect(users.length).toBe(4); - - // Ensure only the four users created in beforeAll are present - const expectedUserUuids = [ - globalThis.creator.uuid, - globalThis.user.uuid, - globalThis.externalUser.uuid, - globalThis.admin.uuid, - ]; - const actualUserUuids = users.map((user) => user.uuid); - expect(actualUserUuids.sort()).toEqual(expectedUserUuids.sort()); - - // delete all action templates - const actionTemplateRepository = - database.getRepository(ActionTemplateEntity); - const allActionTemplates = await actionTemplateRepository.find(); - - console.log('DEBUG: all templates', allActionTemplates); - - await actionTemplateRepository.remove(allActionTemplates); - const remainingActionTemplates = await actionTemplateRepository.find(); - - expect(remainingActionTemplates.length).toBe(0); - console.log(`[DEBUG]: All Action Template removed.`); - - // delete all actions - const actionsRepository = - database.getRepository(ActionEntity); - const allActions = await actionsRepository.find(); - await actionsRepository.remove(allActions); - const remainingActions = await actionsRepository.find(); - - expect(remainingActions.length).toBe(0); - console.log(`[DEBUG]: All Actions removed.`); - - // delete all missions - const missionRepository = - database.getRepository(MissionEntity); - const allMissions = await missionRepository.find(); - await missionRepository.remove(allMissions); - const remainingMissions = await missionRepository.find(); - expect(remainingMissions.length).toBe(0); - console.log(`[DEBUG]: All Missions removed.`); - - // delete project - const projectRepository = - database.getRepository(ProjectEntity); - const allProjects = await projectRepository.find(); - await projectRepository.remove(allProjects); - const remainingProjects = await projectRepository.find(); - - expect(remainingProjects.length).toBe(0); - console.log(`[DEBUG]: All Projects removed.`); - }); - - // afterAll(async () => { - // await clearAllData(); - // await database.destroy(); - // }); - - test('if a internal user with create rights can create a action template', async () => { - // created in beforeEach() - const actionRepository = - database.getRepository(ActionTemplateEntity); - const action = await actionRepository.findOneOrFail({ - where: { uuid: globalThis.actionUuid }, - }); - expect(action['name']).toBe('test_action'); - const actions = await actionRepository.find(); - expect(actions.length).toBe(1); - }); - - test('if a internal user with create rights can submit a action template', async () => { - const headersBuilder = new HeaderCreator(globalThis.creator); - headersBuilder.addHeader('Content-Type', 'application/json'); - - const response = await fetch(`${DEFAULT_URL}/action/submit`, { - method: 'POST', - headers: headersBuilder.getHeaders(), - body: JSON.stringify({ - missionUUID: globalThis.missionUuid, - templateUUID: globalThis.actionUuid, - } as SubmitActionDto), - credentials: 'include', - }); - - const json = await response.json(); - console.log(`['DEBUG'] Created action:`, json); - expect(response.status).toBeLessThan(300); - }); - - // test('if a user with DELETE rights can create a action template', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // // file handling tests - // test('if file is uploaded and can be downloaded again inside an action', async () => { - // // TODO: implement this test - // expect(true).toBe(true); - // // const filename = 'test_small.bag'; - - // // const userId = await mockDatabaseUser('internal@leggedrobotics.com'); - // // const user = await getUserFromDatabase(userId); - // // // create project - // // const projectUuid = await createProjectUsingPost( - // // { - // // name: 'test_project', - // // description: 'test description', - // // requiredTags: [], - // // }, - // // user, - // // ); - // // expect(projectUuid).toBeDefined(); - - // // // create mission using the post - // // const missionUuid = await createMissionUsingPost( - // // { - // // name: 'test_mission', - // // projectUUID: projectUuid, - // // tags: {}, - // // ignoreTags: true, - // // }, - // // user, - // // ); - // // expect(missionUuid).toBeDefined(); - - // // const fileHash = await uploadFile(user, filename, missionUuid); - - // // const createTemplate = await fetch( - // // `http://localhost:3000/action/createTemplate`, - // // { - // // method: 'POST', - // // headers: { - // // cookie: `authtoken=${getJwtToken(user)}`, - // // 'Content-Type': 'application/json', - // // }, - // // body: JSON.stringify({ - // // name: 'test-template', - // // command: '', - // // image: 'rslethz/action:file-hash-latest', - // // cpuCores: 2, - // // cpuMemory: 2, - // // gpuMemory: -1, - // // maxRuntime: 2, - // // searchable: true, - // // accessRights: AccessGroupRights.READ, - // // }), - // // }, - // // ); - - // // expect(createTemplate.status).toBeLessThan(300); - // // const res = await createTemplate.json(); - // // const uuid = res.uuid; - - // // // start action container - // // const actionSubmission = await fetch( - // // `http://localhost:3000/action/submit`, - // // { - // // method: 'POST', - // // headers: { - // // cookie: `authtoken=${getJwtToken(user)}`, - // // 'Content-Type': 'application/json', - // // }, - // // body: JSON.stringify({ - // // missionUUID: missionUuid, - // // templateUUID: uuid, - // // } as SubmitActionDto), - // // }, - // // ); - - // // // check if the request was successful - // // expect(actionSubmission.status).toBeLessThan(300); - - // // // get action uuid - // // const action = await actionSubmission.json(); - // // const actionUuid: string = action.uuid; - // // expect(actionUuid).toBeDefined(); - // // let logs: any[] = []; - // // await new Promise((resolve) => setTimeout(resolve, 2000)); - - // // while (true) { - // // const _res = await fetch( - // // `http://localhost:3000/action/details?uuid=${actionUuid}`, - // // { - // // method: 'GET', - // // headers: { - // // cookie: `authtoken=${getJwtToken(user)}`, - // // }, - // // }, - // // ); - - // // const json = await _res.json(); - // // if ( - // // json.state === ActionState.DONE || - // // json.state === ActionState.FAILED - // // ) { - // // logs = json.logs; - // // console.log('exiting:', json.state); - // // break; - // // } - - // // await new Promise((resolve) => setTimeout(resolve, 1000)); - // // } - - // // const fileHashString = Buffer.from(fileHash).toString('hex'); - // // console.log(fileHashString); - - // // expect(logs).toBeDefined(); - // // const messages = logs.map((log) => log.message) ?? []; - // // console.log(messages); - // // const containsFile = messages.some((message) => - // // message.includes(fileHashString), - // // ); - // // expect(containsFile).toBeTruthy(); - - // // submit a new action - // }, 30_000); - - // test('if you can upload a file within an action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // test('if you can load an existing action and submit it', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // test('if you can load an existing action and save it as a new version', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // test('if you can view details of an action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // // user: read access - // test('if a user with read (viewer) access on a mission can view an action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // test('if a user with read access (viewer) on a mission can not create an action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // // test('if a user with read access (viewer) cannot cancel an action', () => { - // // // TODO: implement this test - // // expect(true).toBe(true); - // // }); - - // test('if a user with read access (viewer) cannot delete an action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // // user: create/edit access - // test('if a user with edit/create access (creator) on a mission can create an action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // // test('if a user with edit/create access (creator) on a mission cannot cancel an action', () => { - // // // TODO: implement this test - // // expect(true).toBe(true); - // // }); - - // test('if a user with edit/create access (creator) on a mission can delete an action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // // admin - // test('if a admin can view details of any action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // test('if a admin can create an action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); - - // // test('if a admin can cancel any action', () => { - // // // TODO: implement this test - // // expect(true).toBe(true); - // // }); - - // test('if a admin can delete any action', () => { - // // TODO: implement this test - // expect(true).toBe(true); - // }); -}); diff --git a/backend/tests/actions/test-actions.test.ts b/backend/tests/actions/test-actions.test.ts new file mode 100644 index 000000000..029d7e273 --- /dev/null +++ b/backend/tests/actions/test-actions.test.ts @@ -0,0 +1,483 @@ +import { ActionDto } from '@kleinkram/api-dto/types/actions/action.dto'; +import { CreateTemplateDto } from '@kleinkram/api-dto/types/actions/create-template.dto'; +import { SubmitActionDto } from '@kleinkram/api-dto/types/submit-action-response.dto'; +import { + AccessGroupEntity, + ActionEntity, + ActionTemplateEntity, + MissionEntity, + ProjectEntity, + UserEntity, + WorkerEntity, +} from '@kleinkram/backend-common'; +import { AccessGroupRights, ActionState } from '@kleinkram/shared'; +import { DEFAULT_URL, generateAndFetchDatabaseUser } from '../auth/utilities'; +import { + createMissionUsingPost, + createProjectUsingPost, + HeaderCreator, +} from '../utils/api-calls'; +import { clearAllData, database } from '../utils/database-utilities'; + +describe('Verify Action (Templates & Runs)', () => { + // Helper to create a template via API (simulating frontend behavior) + const createTemplateViaApi = async ( + user: UserEntity, + dto: CreateTemplateDto, + // eslint-disable-next-line unicorn/consistent-function-scoping + ) => { + const headersBuilder = new HeaderCreator(user); + headersBuilder.addHeader('Content-Type', 'application/json'); + + const response = await fetch(`${DEFAULT_URL}/templates`, { + method: 'POST', + headers: headersBuilder.getHeaders(), + body: JSON.stringify(dto), + }); + + if (response.status >= 300) { + throw new Error( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Failed to create template: ${response.status} ${response.statusText}`, + ); + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return response.json(); + }; + + beforeAll(async () => { + await database.initialize(); + await clearAllData(); + + console.log(`[DEBUG]: Global url: ${DEFAULT_URL}`); + + // Create internal user (Creator) + ({ + user: globalThis.creator as UserEntity, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + token: globalThis.creator.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + response: globalThis.creator.Response, + } = await generateAndFetchDatabaseUser('internal', 'user')); + + // Create 2nd internal user (Standard User) + ({ + user: globalThis.user as UserEntity, + token: globalThis.userToken, + response: globalThis.userResponse, + } = await generateAndFetchDatabaseUser('internal', 'user')); + + // Create external user + ({ + user: globalThis.externalUser as UserEntity, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + token: globalThis.externalUser.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + response: globalThis.externalUser.response, + } = await generateAndFetchDatabaseUser('external', 'user')); + + // Create admin user + ({ + user: globalThis.admin as UserEntity, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + token: globalThis.admin.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + response: globalThis.admin.response, + } = await generateAndFetchDatabaseUser('internal', 'admin')); + + // Create Worker + const workerRepo = database.getRepository(WorkerEntity); + const worker = workerRepo.create({ + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + identifier: `test-worker-actions-id-${Date.now()}`, + hostname: 'test-host-actions', + reachable: true, + lastSeen: new Date(), + cpuMemory: 1000, + cpuCores: 4, + cpuModel: 'test-cpu', + storage: 1000, + gpuMemory: -1, + }); + await workerRepo.save(worker); + + // Initialize queue for this worker + // await new Promise((resolve) => setTimeout(resolve, 35000)); + }); + + beforeEach(async () => { + // 1. Setup Access Groups + const accessGroupRepository = + database.getRepository(AccessGroupEntity); + const accessGroupCreator = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + where: { name: globalThis.creator.name }, + }); + const accessGroupUser = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + where: { name: globalThis.user.name }, + }); + + // 2. Generate Project + // Creator has DELETE rights, User has CREATE rights + globalThis.projectUuid = await createProjectUsingPost( + { + name: 'test_project', + description: 'This is a test project', + requiredTags: [], + accessGroups: [ + { + rights: AccessGroupRights.DELETE, + accessGroupUUID: accessGroupCreator.uuid, + }, + { + rights: AccessGroupRights.CREATE, + accessGroupUUID: accessGroupUser.uuid, + }, + ], + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + ); + + // 3. Create Mission + globalThis.missionUuid = await createMissionUsingPost( + { + name: 'test_mission', + projectUUID: globalThis.projectUuid, + tags: {}, + ignoreTags: true, + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + ); + + // 4. Create Action Template (Using the new API endpoint) + const templateDto: CreateTemplateDto = { + name: 'test_action_template', + description: 'Test action template used in integration test', + command: '', + cpuCores: 2, + cpuMemory: 2, + entrypoint: 'd', + gpuMemory: -1, + dockerImage: 'rslethz/test', + accessRights: AccessGroupRights.DELETE, // Restricted rights + maxRuntime: 1, + }; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const createdTemplate = await createTemplateViaApi( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + globalThis.creator, + templateDto, + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + globalThis.templateUuid = createdTemplate.uuid; + + console.log( + `[DEBUG]: Template created with UUID: ${globalThis.templateUuid}`, + ); + }); + + afterEach(async () => { + // Cleanup entities + const repos = [ + { name: 'Actions', entity: ActionEntity }, + { name: 'Templates', entity: ActionTemplateEntity }, + { name: 'Missions', entity: MissionEntity }, + { name: 'Projects', entity: ProjectEntity }, + ]; + + for (const repo of repos) { + const repository = database.getRepository(repo.entity); + const items = await repository.find(); + if (items.length > 0) { + await repository.remove(items); + } + console.log(`[DEBUG]: All ${repo.name} removed.`); + } + }); + + afterAll(async () => { + await clearAllData(); + await database.destroy(); + }); + + test('if a internal user with rights can create a action template', async () => { + // Verification is essentially done in beforeEach, but we verify DB state here + const templateRepository = + database.getRepository(ActionTemplateEntity); + const template = await templateRepository.findOneOrFail({ + where: { uuid: globalThis.templateUuid }, + }); + + expect(template.name).toBe('test_action_template'); + expect(template.image_name).toBe('rslethz/test'); + }); + + test('if a internal user with rights can submit (dispatch) an action', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const headersBuilder = new HeaderCreator(globalThis.creator); + headersBuilder.addHeader('Content-Type', 'application/json'); + + // New Endpoint: POST /actions + const response = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: headersBuilder.getHeaders(), + body: JSON.stringify({ + missionUUID: globalThis.missionUuid, + templateUUID: globalThis.templateUuid, + } as SubmitActionDto), + }); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const json = await response.json(); + expect(response.status).toBeLessThan(300); + expect(json).toHaveProperty('actionUUID'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const uuid = json.actionUUID; + console.log('[DEBUG] Assigned UUID:', uuid); + + // Verify in DB + const actionRepo = database.getRepository(ActionEntity); + const savedAction = await actionRepo.findOne({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + where: { uuid: json.uuid }, + }); + expect(savedAction).toBeDefined(); + }); + + test('if a user can view details of a submitted action', async () => { + // Debug: Check /user/me + const meResponse = await fetch(`${DEFAULT_URL}/user/me`, { + method: 'GET', + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + headers: new HeaderCreator(globalThis.creator).getHeaders(), + }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const me = await meResponse.json(); + console.log('[DEBUG] /user/me:', me); + + // 1. Submit Action first + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const headers = new HeaderCreator(globalThis.creator); + headers.addHeader('Content-Type', 'application/json'); + const submitResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: headers.getHeaders(), + body: JSON.stringify({ + missionUUID: globalThis.missionUuid, + templateUUID: globalThis.templateUuid, + } as SubmitActionDto), + }); + if (submitResponse.status !== 201) { + const errorText = await submitResponse.text(); + console.log('[DEBUG] Submit error:', errorText); + } + expect(submitResponse.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const submitJson = await submitResponse.json(); + console.log('[DEBUG] Submit JSON:', submitJson); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { actionUUID: uuid } = submitJson; + + // 2. Fetch Details (GET /actions/:uuid) + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const detailsResponse = await fetch(`${DEFAULT_URL}/actions/${uuid}`, { + method: 'GET', + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + headers: new HeaderCreator(globalThis.creator).getHeaders(), + }); + + if (detailsResponse.status !== 200) { + console.log('[DEBUG] Details error:', await detailsResponse.text()); + } + expect(detailsResponse.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const details: ActionDto = await detailsResponse.json(); + expect(details.uuid).toBe(uuid); + expect(details.template.name).toBe('test_action_template'); + }); + + test('if a user can get logs of a submitted action', async () => { + // 1. Submit Action first + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const headers = new HeaderCreator(globalThis.creator); + headers.addHeader('Content-Type', 'application/json'); + const submitResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: headers.getHeaders(), + body: JSON.stringify({ + missionUUID: globalThis.missionUuid, + templateUUID: globalThis.templateUuid, + } as SubmitActionDto), + }); + expect(submitResponse.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { actionUUID: uuid } = await submitResponse.json(); + + // 2. Fetch Logs (GET /actions/:uuid/logs) + const logsResponse = await fetch( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `${DEFAULT_URL}/actions/${uuid}/logs?skip=0&take=10`, + { + method: 'GET', + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + headers: new HeaderCreator(globalThis.creator).getHeaders(), + }, + ); + + expect(logsResponse.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const logs = await logsResponse.json(); + expect(logs).toHaveProperty('data'); + expect(logs).toHaveProperty('count'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(Array.isArray(logs.data)).toBe(true); + // If there are logs, verify their structure + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (logs.data.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const log = logs.data[0]; + expect(log).toHaveProperty('timestamp'); + expect(log).toHaveProperty('message'); + expect(log).toHaveProperty('type'); + } + }); + + test('if a user can batch submit actions', async () => { + // POST /actions/batch + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const headers = new HeaderCreator(globalThis.creator); + headers.addHeader('Content-Type', 'application/json'); + const response = await fetch(`${DEFAULT_URL}/actions/batch`, { + method: 'POST', + headers: headers.getHeaders(), + body: JSON.stringify({ + missionUUIDs: [globalThis.missionUuid], + templateUUID: globalThis.templateUuid, + }), + }); + + expect(response.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const json = await response.json(); + expect(Array.isArray(json)).toBe(true); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(json.length).toBe(1); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(json[0]).toHaveProperty('actionUUID'); + }); + + test('if a user can list all actions', async () => { + // GET /actions + const response = await fetch(`${DEFAULT_URL}/actions?skip=0&take=10`, { + method: 'GET', + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + headers: new HeaderCreator(globalThis.creator).getHeaders(), + }); + + expect(response.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const json = await response.json(); + expect(json).toHaveProperty('data'); + expect(json).toHaveProperty('count'); + }); + + test('if a user with DELETE rights can delete an action run', async () => { + // 1. Submit Action + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const headers = new HeaderCreator(globalThis.creator); + headers.addHeader('Content-Type', 'application/json'); + const submitResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: headers.getHeaders(), + body: JSON.stringify({ + missionUUID: globalThis.missionUuid, + templateUUID: globalThis.templateUuid, + } as SubmitActionDto), + }); + expect(submitResponse.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { actionUUID: uuid } = await submitResponse.json(); + + // Force update action state to DONE so it can be deleted + const actionRepo = database.getRepository(ActionEntity); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + await actionRepo.update({ uuid }, { state: ActionState.DONE }); + + // 2. Delete Action (DELETE /actions/:uuid) + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const deleteResponse = await fetch(`${DEFAULT_URL}/actions/${uuid}`, { + method: 'DELETE', + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + headers: new HeaderCreator(globalThis.creator).getHeaders(), + }); + + expect(deleteResponse.status).toBe(200); + + // 3. Verify deletion in DB + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const deletedAction = await actionRepo.findOne({ where: { uuid } }); + expect(deletedAction).toBeNull(); + }); + + test('if an admin can list all action templates', async () => { + // New Endpoint: GET /templates + const response = await fetch( + `${DEFAULT_URL}/templates?skip=0&take=10`, + { + method: 'GET', + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + headers: new HeaderCreator(globalThis.admin).getHeaders(), + }, + ); + + expect(response.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const json = await response.json(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(json.data.length).toBeGreaterThanOrEqual(1); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(json.data[0].name).toBe('test_action_template'); + }); + + // Permission Test Example + test('if a user WITHOUT rights cannot delete an action', async () => { + // 1. Creator submits action (Creator has DELETE rights on project) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const headers = new HeaderCreator(globalThis.creator); + headers.addHeader('Content-Type', 'application/json'); + const submitResponse = await fetch(`${DEFAULT_URL}/actions`, { + method: 'POST', + headers: headers.getHeaders(), + body: JSON.stringify({ + missionUUID: globalThis.missionUuid, + templateUUID: globalThis.templateUuid, + } as SubmitActionDto), + }); + if (submitResponse.status !== 201) { + const errorText = await submitResponse.text(); + console.log('[DEBUG] Submit error:', errorText); + } + expect(submitResponse.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { actionUUID: uuid } = await submitResponse.json(); + + // Force update action state to DONE so it can be deleted + const actionRepo = database.getRepository(ActionEntity); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + await actionRepo.update({ uuid }, { state: ActionState.DONE }); + + // 2. External User tries to delete it (Assuming External has no rights) + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const deleteResponse = await fetch(`${DEFAULT_URL}/actions/${uuid}`, { + method: 'DELETE', + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + headers: new HeaderCreator(globalThis.externalUser).getHeaders(), + }); + + expect(deleteResponse.status).toBe(403); // Assuming Forbidden + }); +}); diff --git a/backend/tests/auth/access-groups/access-groups.test.ts b/backend/tests/auth/access-groups/access-groups.test.ts index cce7599bd..b64d4a7a6 100644 --- a/backend/tests/auth/access-groups/access-groups.test.ts +++ b/backend/tests/auth/access-groups/access-groups.test.ts @@ -16,7 +16,8 @@ describe('Verify Access Groups External', () => { }); // user: external - test('Non "kleinkram.leggedrobotics.com" email is not added to default group', async () => { + // eslint-disable-next-line @typescript-eslint/require-await + test('Non "kleinkram.dev" email is not added to default group', async () => { // TODO: implement this test expect(true).toBe(true); @@ -79,11 +80,12 @@ describe('Verify Access Groups Internal', () => { }); // user: internal + // eslint-disable-next-line @typescript-eslint/require-await test('if leggedrobotics email is added to default group', async () => { // TODO: implement this test expect(true).toBe(true); - // const mockEmail = 'internal-user@kleinkram.leggedrobotics.com'; + // const mockEmail = 'internal-user@kleinkram.dev'; // const internalUuid = await mockDatabaseUser(mockEmail); // const accessGroups = await getAllAccessGroups(); @@ -107,7 +109,7 @@ describe('Verify Access Groups Internal', () => { // select: ['email'], // }); - // expect(user.email).toBe('internal-user@kleinkram.leggedrobotics.com'); + // expect(user.email).toBe('internal-user@kleinkram.dev'); }); test('if primary group cannot be deleted', () => { @@ -242,6 +244,7 @@ describe('Verify Access Groups Internal User Access', () => { await database.destroy(); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if a user with create rights can generate a access group', async () => { // TODO: implement this test expect(true).toBe(true); diff --git a/backend/tests/auth/anonymous-users-trigger-401.test.ts b/backend/tests/auth/anonymous-users-trigger-401.test.ts index 9c0d934c4..514662e68 100644 --- a/backend/tests/auth/anonymous-users-trigger-401.test.ts +++ b/backend/tests/auth/anonymous-users-trigger-401.test.ts @@ -9,10 +9,13 @@ const UNAUTHENTICATED_ENDPOINTS: string[] = [ '/auth/github/callback', '/auth/fake-oauth', '/auth/fake-oauth/callback', + '/auth/available-providers', '/auth/logout', '/metrics', '/swagger', '/swagger/', + '/integrations/', + '/api/health', ]; /** @@ -32,6 +35,7 @@ describe('Unauthenticated users trigger 401', () => { for (const endpoint of endpoints) { // skip endpoints that are not protected if ( + // eslint-disable-next-line @typescript-eslint/naming-convention UNAUTHENTICATED_ENDPOINTS.some((url_prefix: string) => endpoint.url.startsWith(url_prefix), ) diff --git a/backend/tests/auth/auth-flow/jwt.test.ts b/backend/tests/auth/auth-flow/jwt.test.ts index e67396cf6..a32bf3ef5 100644 --- a/backend/tests/auth/auth-flow/jwt.test.ts +++ b/backend/tests/auth/auth-flow/jwt.test.ts @@ -38,7 +38,8 @@ describe('Verify JWT Handling', () => { // Use the already imported jwt module const token = jwt.sign( { user: { uuid: '' } }, - process.env['JWT_SECRET'] || 'default-secret', + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + process.env.JWT_SECRET || 'default-secret', ); const headersBuilder = new HeaderCreator(); diff --git a/backend/tests/auth/database/database-access.test.ts b/backend/tests/auth/database/database-access.test.ts index 94a1f0663..c9e70ea5b 100644 --- a/backend/tests/auth/database/database-access.test.ts +++ b/backend/tests/auth/database/database-access.test.ts @@ -16,6 +16,7 @@ describe('Verify Project Level Access', () => { }); // descrition + // eslint-disable-next-line @typescript-eslint/require-await test('if user ...', async () => { // TODO: implement this test expect(true).toBe(true); diff --git a/backend/tests/auth/missions/mission-access.test.ts b/backend/tests/auth/missions/mission-access.test.ts index 55c2a65c8..ebce093ec 100644 --- a/backend/tests/auth/missions/mission-access.test.ts +++ b/backend/tests/auth/missions/mission-access.test.ts @@ -19,36 +19,42 @@ describe('Verify Mission Level Admin Access', () => { // admin: delete access by default + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can create a mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can view any mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can edit metadata of an mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can move any mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can delete any mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can view any files in a mission', async () => { //TODO: implement this test @@ -57,36 +63,42 @@ describe('Verify Mission Level Admin Access', () => { // admin: file access + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can view any file in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can edit any file in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can download any file in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can upload any file to a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can move any file in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if admin can delete any file in a mission', async () => { //TODO: implement this test @@ -107,36 +119,42 @@ describe('Verify Mission Level User Access', () => { // user: read/create access + // eslint-disable-next-line @typescript-eslint/require-await test('if user with create access on an project can create a mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on an project can view any mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on an project cannot edit a mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on an project cannot edit metadata of an mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on an project cannot move a mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on an project cannot delete a mission', async () => { // TODO: implement this test @@ -144,24 +162,28 @@ describe('Verify Mission Level User Access', () => { }); // user: modify access + // eslint-disable-next-line @typescript-eslint/require-await test('if user with modify/edit access on an project can edit a mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with modify/edit access on an project can edit metadata of an mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with modify/edit access on an project cannot move a mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with modify/edit access on an project cannot delete a mission', async () => { // TODO: implement this test @@ -169,12 +191,14 @@ describe('Verify Mission Level User Access', () => { }); // user: delete access + // eslint-disable-next-line @typescript-eslint/require-await test('if user with modify/edit access on an project can move a mission', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with delete access on an project can delete a mission', async () => { //TODO: implement this test @@ -195,42 +219,49 @@ describe('Verify Mission File Level User Access', () => { // files in missions tests // user: read access + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on a mission can view files in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on a mission cannot edit files in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on a mission can download files in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on a mission can upload file from google drive into mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on a mission can upload file from local drive into mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on a mission cannot move files in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with read access on a mission cannot delete an file in a mission', async () => { //TODO: implement this test @@ -239,18 +270,21 @@ describe('Verify Mission File Level User Access', () => { // user: edit access + // eslint-disable-next-line @typescript-eslint/require-await test('if user with edit access on a mission can edit files in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with edit access on a mission cannot move files in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with edit access on a mission cannot delete an file in a mission', async () => { //TODO: implement this test @@ -258,12 +292,14 @@ describe('Verify Mission File Level User Access', () => { }); // user: delete access + // eslint-disable-next-line @typescript-eslint/require-await test('if user with delete access on a mission can move files in a mission', async () => { //TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user with delete access on a mission can delete an file in a mission', async () => { //TODO: implement this test diff --git a/backend/tests/auth/project/project-access.test.ts b/backend/tests/auth/project/project-access.test.ts index 396480c12..e5493e902 100644 --- a/backend/tests/auth/project/project-access.test.ts +++ b/backend/tests/auth/project/project-access.test.ts @@ -17,41 +17,49 @@ describe('Verify Project Groups Access', () => { }); // access Group Tests + // eslint-disable-next-line @typescript-eslint/require-await test('if user can add project with read access to existing access group', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user can add project with create access to existing access group', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user can add project with write access to existing access group', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user can add project with delete access to existing access group', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user can add multiple projects with read rights to existing access group', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user can add multiple projects with create rights to existing access group', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user can add multiple projects with write rights to existing access group', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if user can add multiple projects with delete rights to existing access group', async () => { // TODO: implement this test expect(true).toBe(true); diff --git a/backend/tests/auth/project/project-creation-endpoint.test.ts b/backend/tests/auth/project/project-creation-endpoint.test.ts index b613c89c4..9c64c88db 100644 --- a/backend/tests/auth/project/project-creation-endpoint.test.ts +++ b/backend/tests/auth/project/project-creation-endpoint.test.ts @@ -1,10 +1,10 @@ -import { clearAllData, database } from '../../utils/database-utilities'; +import { database } from '../../utils/database-utilities'; +import { setupDatabaseHooks } from '../../utils/test-helpers'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { AccessGroupRights } from '@common/frontend_shared/enum'; +import { AccessGroupEntity, ProjectEntity } from '@kleinkram/backend-common'; +import { MissionEntity } from '@kleinkram/backend-common/entities/mission/mission.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { AccessGroupRights } from '@kleinkram/shared'; import { createMissionUsingPost, createProjectUsingPost, @@ -19,58 +19,54 @@ import { DEFAULT_URL, generateAndFetchDatabaseUser } from '../utilities'; */ describe('Verification project endpoint', () => { - beforeAll(async () => { - await database.initialize(); - await clearAllData(); + setupDatabaseHooks(); + let creator: UserEntity; + let user: UserEntity; + let externalUser: UserEntity; + let admin: UserEntity; + let projectUuid: string; + + beforeEach(async () => { + // global url set in utilities // global url set in utilities - console.log(`[DEBUG]: Global url: ${DEFAULT_URL}`); // Create internal user - ({ - user: globalThis.creator as UserEntity, - token: globalThis.creator.token, - response: globalThis.creator.Response, - } = await generateAndFetchDatabaseUser('internal', 'user')); - console.log(`[DEBUG]: Global creator: ${globalThis.creator.name}`); + const creatorData = await generateAndFetchDatabaseUser( + 'internal', + 'user', + ); + creator = creatorData.user; // Create 2nd internal user - ({ - user: globalThis.user as UserEntity, - token: globalThis.userToken, - response: globalThis.userResponse, - } = await generateAndFetchDatabaseUser('internal', 'user')); - console.log(`[DEBUG]: Global user: ${globalThis.user.name}`); + const userData = await generateAndFetchDatabaseUser('internal', 'user'); + user = userData.user; + user = userData.user; // Create external user - ({ - user: globalThis.externalUser as UserEntity, - token: globalThis.externalUser.token, - response: globalThis.externalUser.response, - } = await generateAndFetchDatabaseUser('external', 'user')); - console.log( - `[DEBUG]: Global external user: ${globalThis.externalUser.name}`, + const externalUserData = await generateAndFetchDatabaseUser( + 'external', + 'user', ); + externalUser = externalUserData.user; + externalUser = externalUserData.user; // Create admin user - ({ - user: globalThis.admin as UserEntity, - token: globalThis.admin.token, - response: globalThis.admin.response, - } = await generateAndFetchDatabaseUser('internal', 'admin')); - console.log(`[DEBUG]: Global admin: ${globalThis.admin.name}`); - }); - - beforeEach(async () => { + const adminData = await generateAndFetchDatabaseUser( + 'internal', + 'admin', + ); + admin = adminData.user; + admin = adminData.user; // get access group for creator const accessGroupRepository = database.getRepository(AccessGroupEntity); const accessGroupCreator = await accessGroupRepository.findOneOrFail({ - where: { name: globalThis.creator.name }, + where: { name: creator.name }, }); // generate project with creator - globalThis.projectUuid = await createProjectUsingPost( + projectUuid = await createProjectUsingPost( { name: 'test_project', description: 'This is a test project', @@ -82,19 +78,19 @@ describe('Verification project endpoint', () => { }, ], }, - globalThis.creator, + creator, ); // check if project is created const projectRepository = database.getRepository(ProjectEntity); const project = await projectRepository.findOneOrFail({ - where: { uuid: globalThis.projectUuid }, + where: { uuid: projectUuid }, }); expect(project.name).toBe('test_project'); expect(project.description).toBe('This is a test project'); - expect(project.uuid).toBe(globalThis.projectUuid); + expect(project.uuid).toBe(projectUuid); }); afterEach(async () => { @@ -105,10 +101,10 @@ describe('Verification project endpoint', () => { // Ensure only the four users created in beforeAll are present const expectedUserUuids = [ - globalThis.creator.uuid, - globalThis.user.uuid, - globalThis.externalUser.uuid, - globalThis.admin.uuid, + creator.uuid, + user.uuid, + externalUser.uuid, + admin.uuid, ]; const actualUserUuids = users.map((user) => user.uuid); expect(actualUserUuids.sort()).toEqual(expectedUserUuids.sort()); @@ -117,39 +113,38 @@ describe('Verification project endpoint', () => { const missionRepository = database.getRepository(MissionEntity); const allMissions = await missionRepository.find(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const responseMission = await missionRepository.remove(allMissions); const remainingMissions = await missionRepository.find(); expect(remainingMissions.length).toBe(0); - console.log(`[DEBUG]: All Missions removed: ${responseMission}`); + expect(remainingMissions.length).toBe(0); // delete project const projectRepository = database.getRepository(ProjectEntity); const allProjects = await projectRepository.find(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const response = await projectRepository.remove(allProjects); const remainingProjects = await projectRepository.find(); expect(remainingProjects.length).toBe(0); - console.log(`[DEBUG]: All Projects removed: ${response}`); + expect(remainingProjects.length).toBe(0); }); - afterAll(async () => { - await clearAllData(); - await database.destroy(); - }); + // afterAll handled by setupDatabaseHooks test('User with leggedrobotics email can create a new project', async () => { // happens in beforeAll const projectRepository = database.getRepository(ProjectEntity); const project = await projectRepository.findOneOrFail({ - where: { uuid: globalThis.projectUuid }, + where: { uuid: projectUuid }, }); - expect(project['name']).toBe('test_project'); + expect(project.name).toBe('test_project'); }); test('if it is not possible to create a project with the same name', async () => { - const header = new HeaderCreator(globalThis.creator); + const header = new HeaderCreator(creator); const response = await fetch(`${DEFAULT_URL}/projects`, { method: 'POST', headers: header.getHeaders(), @@ -163,22 +158,21 @@ describe('Verification project endpoint', () => { test('if user with leggedrobotics email have read only access by default', async () => { // get project with user 2 - const header = new HeaderCreator(globalThis.user); - const response = await fetch( - `${DEFAULT_URL}/projects/${globalThis.projectUuid}`, - { - method: 'GET', - headers: header.getHeaders(), - }, - ); + const header = new HeaderCreator(user); + const response = await fetch(`${DEFAULT_URL}/projects/${projectUuid}`, { + method: 'GET', + headers: header.getHeaders(), + }); expect(response.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const projectResponse = await response.json(); - expect(projectResponse['name']).toBe('test_project'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(projectResponse.name).toBe('test_project'); // check denied modification access with user2 header.addHeader('Content-Type', 'application/json'); const response2 = await fetch( - `${DEFAULT_URL}/projects/${globalThis.projectUuid}`, + `${DEFAULT_URL}/projects/${projectUuid}`, { method: 'PUT', headers: header.getHeaders(), @@ -193,7 +187,7 @@ describe('Verification project endpoint', () => { // check denied delete access const response3 = await fetch( - `${DEFAULT_URL}/projects/${globalThis.projectUuid}`, + `${DEFAULT_URL}/projects/${projectUuid}`, { method: 'DELETE', headers: header.getHeaders(), @@ -205,10 +199,10 @@ describe('Verification project endpoint', () => { const projectRepository = database.getRepository(ProjectEntity); const project = await projectRepository.findOneOrFail({ - where: { uuid: globalThis.projectUuid }, + where: { uuid: projectUuid }, }); - expect(project['name']).toBe('test_project'); - expect(project['uuid']).toBe(globalThis.projectUuid); + expect(project.name).toBe('test_project'); + expect(project.uuid).toBe(projectUuid); const projects = await projectRepository.find(); expect(projects.length).toBe(1); @@ -216,8 +210,8 @@ describe('Verification project endpoint', () => { test('the creator of a project has delete access to the project', async () => { // delete the project - const headerCreator = new HeaderCreator(globalThis.creator); - const url = `${DEFAULT_URL}/projects/${globalThis.projectUuid}`; + const headerCreator = new HeaderCreator(creator); + const url = `${DEFAULT_URL}/projects/${projectUuid}`; const response = await fetch(url, { method: 'DELETE', headers: headerCreator.getHeaders(), @@ -240,15 +234,15 @@ describe('Verification project endpoint', () => { accessGroups: [ { rights: AccessGroupRights.READ, - userUUID: globalThis.user.uuid, + userUUID: user.uuid, }, ], }, - globalThis.creator, + creator, ); // check if project can be manipulated by user2 - const headerCreator = new HeaderCreator(globalThis.user); + const headerCreator = new HeaderCreator(user); const response = await fetch(`${DEFAULT_URL}/projects/${projectUuid}`, { method: 'PUT', headers: headerCreator.getHeaders(), @@ -275,10 +269,10 @@ describe('Verification project endpoint', () => { const accessGroupRepository = database.getRepository(AccessGroupEntity); const accessGroupCreator = await accessGroupRepository.findOneOrFail({ - where: { name: globalThis.creator.name }, + where: { name: creator.name }, }); const accessGroupUser = await accessGroupRepository.findOneOrFail({ - where: { name: globalThis.user.name }, + where: { name: user.name }, }); const projectUuid = await createProjectUsingPost( @@ -297,10 +291,10 @@ describe('Verification project endpoint', () => { }, ], }, - globalThis.creator, + creator, ); - const headerCreator = new HeaderCreator(globalThis.user); + const headerCreator = new HeaderCreator(user); headerCreator.addHeader('Content-Type', 'application/json'); const response = await fetch(`${DEFAULT_URL}/projects/${projectUuid}`, { @@ -324,7 +318,7 @@ describe('Verification project endpoint', () => { }); expect(response.status).toBe(200); - const headersBuilder = new HeaderCreator(globalThis.user); + const headersBuilder = new HeaderCreator(user); headersBuilder.addHeader('Content-Type', 'application/json'); // check if project can be deleted by user @@ -358,10 +352,10 @@ describe('Verification project endpoint', () => { const accessGroupRepository = database.getRepository(AccessGroupEntity); const accessGroupCreator = await accessGroupRepository.findOneOrFail({ - where: { name: globalThis.creator.name }, + where: { name: creator.name }, }); const accessGroupUser = await accessGroupRepository.findOneOrFail({ - where: { name: globalThis.user.name }, + where: { name: user.name }, }); const projectUuid = await createProjectUsingPost( @@ -380,7 +374,7 @@ describe('Verification project endpoint', () => { }, ], }, - globalThis.creator, + creator, ); const missionUuid = await createMissionUsingPost( @@ -390,7 +384,7 @@ describe('Verification project endpoint', () => { tags: {}, ignoreTags: true, }, - globalThis.user, + user, ); // check if mission is generated @@ -399,13 +393,13 @@ describe('Verification project endpoint', () => { const mission = await missionRepository.findOneOrFail({ where: { uuid: missionUuid }, }); - expect(mission['name']).toBe('test_mission'); + expect(mission.name).toBe('test_mission'); const missions = await missionRepository.find(); expect(missions.length).toBe(1); - console.log(`[DEBUG]: Mission created with UUID: ${missionUuid}`); + expect(missions.length).toBe(1); // denied permission to delete project because of mission - const creatorHeader = new HeaderCreator(globalThis.creator); + const creatorHeader = new HeaderCreator(creator); creatorHeader.addHeader('Content-Type', 'application/json'); const response = await fetch(`${DEFAULT_URL}/projects/${projectUuid}`, { method: 'DELETE', @@ -441,10 +435,10 @@ describe('Verification project endpoint', () => { // verify mission is deleted const remainingMissions = await missionRepository.find(); expect(remainingMissions.length).toBe(0); - console.log(`[DEBUG]: Mission deleted with UUID ${missionUuid}`); + expect(remainingMissions.length).toBe(0); // check if project can not be deleted by user - const userHeader = new HeaderCreator(globalThis.user); + const userHeader = new HeaderCreator(user); userHeader.addHeader('Content-Type', 'application/json'); const response2 = await fetch( `${DEFAULT_URL}/projects/${projectUuid}`, @@ -473,13 +467,13 @@ describe('Verification project endpoint', () => { test('the creator can add users to with DELETE access to the project during creation', async () => { // create project with delete access for user2 - const headerCreator = new HeaderCreator(globalThis.user); + const headerCreator = new HeaderCreator(user); headerCreator.addHeader('Content-Type', 'application/json'); const accessGroupRepository = database.getRepository(AccessGroupEntity); const accessGroupUser = await accessGroupRepository.findOneOrFail({ - where: { name: globalThis.user.name }, + where: { name: user.name }, }); const projectUuid = await createProjectUsingPost( @@ -493,7 +487,7 @@ describe('Verification project endpoint', () => { }, ], }, - globalThis.creator, + creator, ); // check if project can be deleted by user @@ -508,11 +502,11 @@ describe('Verification project endpoint', () => { const missionUuid = await createMissionUsingPost( { name: 'test_mission', - projectUUID: globalThis.projectUuid, + projectUUID: projectUuid, tags: {}, ignoreTags: true, }, - globalThis.creator, + creator, ); // check if mission is generated @@ -521,27 +515,24 @@ describe('Verification project endpoint', () => { const mission = await missionRepository.findOneOrFail({ where: { uuid: missionUuid }, }); - expect(mission['name']).toBe('test_mission'); + expect(mission.name).toBe('test_mission'); const missions = await missionRepository.find(); expect(missions.length).toBe(1); - console.log(`[DEBUG]: Mission created with UUID: ${missionUuid}`); + expect(missions.length).toBe(1); // denied permission to delete project because of mission - const creatorHeader = new HeaderCreator(globalThis.creator); + const creatorHeader = new HeaderCreator(creator); creatorHeader.addHeader('Content-Type', 'application/json'); - const response = await fetch( - `${DEFAULT_URL}/projects/${globalThis.projectUuid}`, - { - method: 'DELETE', - headers: creatorHeader.getHeaders(), - body: JSON.stringify({ - name: '1234', - description: '1234', - requiredTags: [], - }), - }, - ); + const response = await fetch(`${DEFAULT_URL}/projects/${projectUuid}`, { + method: 'DELETE', + headers: creatorHeader.getHeaders(), + body: JSON.stringify({ + name: '1234', + description: '1234', + requiredTags: [], + }), + }); expect(response.status).toBe(409); // check if project is not deleted diff --git a/backend/tests/auth/project/project-edit-endpoints.test.ts b/backend/tests/auth/project/project-edit-endpoints.test.ts index 62908f761..a01e28d00 100644 --- a/backend/tests/auth/project/project-edit-endpoints.test.ts +++ b/backend/tests/auth/project/project-edit-endpoints.test.ts @@ -1,10 +1,12 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import ProjectAccessEntity from '@common/entities/auth/project-access.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import TagTypeEntity from '@common/entities/tagType/tag-type.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { AccessGroupRights, DataType } from '@common/frontend_shared/enum'; +import { + AccessGroupEntity, + MissionEntity, + ProjectAccessEntity, + ProjectEntity, + TagTypeEntity, + UserEntity, +} from '@kleinkram/backend-common'; +import { AccessGroupRights, DataType } from '@kleinkram/shared'; import { createMetadataUsingPost, createProjectUsingPost, @@ -32,9 +34,12 @@ describe('Verify project manipulation endpoints', () => { // Create internal user ({ user: globalThis.creator as UserEntity, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access token: globalThis.creator.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access response: globalThis.creator.Response, } = await generateAndFetchDatabaseUser('internal', 'user')); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access console.log(`[DEBUG]: Global creator: ${globalThis.creator.name}`); // Create 2nd internal user @@ -43,6 +48,7 @@ describe('Verify project manipulation endpoints', () => { token: globalThis.userToken, response: globalThis.userResponse, } = await generateAndFetchDatabaseUser('internal', 'user')); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access console.log(`[DEBUG]: Global user: ${globalThis.user.name}`); }); @@ -51,11 +57,13 @@ describe('Verify project manipulation endpoints', () => { const accessGroupRepository = database.getRepository(AccessGroupEntity); const accessGroupCreator = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access where: { name: globalThis.creator.name }, }); // get access group for user const accessGroupUser = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access where: { name: globalThis.user.name }, }); @@ -65,6 +73,7 @@ describe('Verify project manipulation endpoints', () => { type: DataType.STRING, name: globalThis.tagName, }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument globalThis.creator, ); @@ -95,6 +104,7 @@ describe('Verify project manipulation endpoints', () => { }, ], }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument globalThis.creator, ); @@ -117,7 +127,9 @@ describe('Verify project manipulation endpoints', () => { // Ensure only the four users created in beforeAll are present const expectedUserUuids = [ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access globalThis.creator.uuid, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access globalThis.user.uuid, ]; const actualUserUuids = users.map((user) => user.uuid); @@ -130,6 +142,7 @@ describe('Verify project manipulation endpoints', () => { const responseMission = await missionRepository.remove(allMissions); const remainingMissions = await missionRepository.find(); expect(remainingMissions.length).toBe(0); + // eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions console.log(`[DEBUG]: All Missions removed: ${responseMission}`); // delete project @@ -140,6 +153,7 @@ describe('Verify project manipulation endpoints', () => { const remainingProjects = await projectRepository.find(); expect(remainingProjects.length).toBe(0); + // eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions console.log(`[DEBUG]: All Projects removed: ${response}`); // delete tags @@ -150,6 +164,7 @@ describe('Verify project manipulation endpoints', () => { const remainingTags = await tagsRepository.find(); expect(remainingTags.length).toBe(0); + // eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions console.log(`[DEBUG]: All Metadata removed: ${metadataResponse}`); }); @@ -181,9 +196,11 @@ describe('Verify project manipulation endpoints', () => { type: DataType.STRING, name: name, }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument globalThis.user, ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const headersBuilder = new HeaderCreator(globalThis.creator); headersBuilder.addHeader('Content-Type', 'application/json'); @@ -214,9 +231,11 @@ describe('Verify project manipulation endpoints', () => { const accessGroupRepository = database.getRepository(AccessGroupEntity); const accessGroupUser = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access where: { name: globalThis.user.name }, }); const accessGroupCreator = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access where: { name: globalThis.creator.name }, }); @@ -248,6 +267,7 @@ describe('Verify project manipulation endpoints', () => { expect(projectCreatorAccess.rights).toBe(AccessGroupRights.DELETE); // edit access rights for access group + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const headersBuilder = new HeaderCreator(globalThis.creator); // check first if not all access groups with delete access can be deleted @@ -296,6 +316,7 @@ describe('Verify project manipulation endpoints', () => { ]), }, ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await response.json(); console.log('[DEBUG]: Response status:', json); expect(response.status).toBe(201); diff --git a/backend/tests/auth/project/user-admin-access.test.ts b/backend/tests/auth/project/user-admin-access.test.ts index a11c3b5b3..34c37005c 100644 --- a/backend/tests/auth/project/user-admin-access.test.ts +++ b/backend/tests/auth/project/user-admin-access.test.ts @@ -1,8 +1,10 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import MissionEntity from '@common/entities/mission/mission.entity'; -import ProjectEntity from '@common/entities/project/project.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { AccessGroupRights } from '@common/frontend_shared/enum'; +import { + AccessGroupEntity, + MissionEntity, + ProjectEntity, + UserEntity, +} from '@kleinkram/backend-common'; +import { AccessGroupRights } from '@kleinkram/shared'; import { HeaderCreator } from '../../utils/api-calls'; import { clearAllData, database } from '../../utils/database-utilities'; import { DEFAULT_URL, generateAndFetchDatabaseUser } from '../utilities'; @@ -23,35 +25,47 @@ describe('Verify project user/admin access', () => { // Create internal user ({ user: globalThis.creator as UserEntity, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access token: globalThis.creator.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access response: globalThis.creator.Response, } = await generateAndFetchDatabaseUser('internal', 'user')); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access console.log(`[DEBUG]: Global creator: ${globalThis.creator.name}`); // Create internal user ({ user: globalThis.user as UserEntity, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access token: globalThis.user.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access response: globalThis.user.Response, } = await generateAndFetchDatabaseUser('internal', 'user')); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access console.log(`[DEBUG]: Global user: ${globalThis.user.name}`); // Create external user ({ user: globalThis.externalUser as UserEntity, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access token: globalThis.externalUser.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access response: globalThis.externalUser.response, } = await generateAndFetchDatabaseUser('external', 'user')); console.log( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access `[DEBUG]: Global external user: ${globalThis.externalUser.name}`, ); // Create admin user ({ user: globalThis.admin as UserEntity, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access token: globalThis.admin.token, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access response: globalThis.admin.response, } = await generateAndFetchDatabaseUser('internal', 'admin')); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access console.log(`[DEBUG]: Global admin: ${globalThis.admin.name}`); }); @@ -60,14 +74,17 @@ describe('Verify project user/admin access', () => { const accessGroupRepository = database.getRepository(AccessGroupEntity); const accessGroupCreator = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access where: { name: globalThis.creator.name }, }); const accessGroupUser = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access where: { name: globalThis.user.name }, }); const accessGroupAdmin = await accessGroupRepository.findOneOrFail({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access where: { name: globalThis.admin.name }, }); @@ -78,10 +95,15 @@ describe('Verify project user/admin access', () => { Array.from({ length: 10 }, async (_, index) => { const project = await projectRepository.save( projectRepository.create({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access creator: { uuid: globalThis.creator.uuid }, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions name: `test_project${index + 1}`, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions description: `This is a test project ${index + 1}`, autoConvert: false, + + // eslint-disable-next-line @typescript-eslint/naming-convention project_accesses: [ { accessGroup: accessGroupCreator, @@ -98,7 +120,7 @@ describe('Verify project user/admin access', () => { ], }), ); - return project['uuid']; + return project.uuid; }), ); const projects = await projectRepository.find(); @@ -113,9 +135,13 @@ describe('Verify project user/admin access', () => { // Ensure only the four users created in beforeAll are present const expectedUserUuids = [ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access globalThis.creator.uuid, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access globalThis.user.uuid, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access globalThis.externalUser.uuid, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access globalThis.admin.uuid, ]; const actualUserUuids = users.map((user) => user.uuid); @@ -148,6 +174,7 @@ describe('Verify project user/admin access', () => { // admin test('if user with admin role can view any project', async () => { // get projects with admin + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const headerCreator = new HeaderCreator(globalThis.admin); for (const [index, uuid] of globalThis.projectUuids.entries()) { @@ -158,12 +185,15 @@ describe('Verify project user/admin access', () => { }); expect(response.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await response.json(); - expect(json['name']).toBe(`test_project${index + 1}`); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions + expect(json.name).toBe(`test_project${index + 1}`); } }); test('if user with admin role can edit any project', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const headerCreator = new HeaderCreator(globalThis.admin); headerCreator.addHeader('Content-Type', 'application/json'); @@ -173,19 +203,24 @@ describe('Verify project user/admin access', () => { method: 'PUT', headers: headerCreator.getHeaders(), body: JSON.stringify({ + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions name: `newName${index}`, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions description: `description${index}`, autoConvert: false, }), }); expect(response.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await response.json(); - expect(json['name']).toBe(`newName${index}`); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions + expect(json.name).toBe(`newName${index}`); } }); test('if user with admin role can delete any project', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const adminHeader = new HeaderCreator(globalThis.admin); adminHeader.addHeader('Content-Type', 'application/json'); @@ -208,6 +243,7 @@ describe('Verify project user/admin access', () => { database.getRepository(MissionEntity); const projectRepository = database.getRepository(ProjectEntity); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const headerCreator = new HeaderCreator(globalThis.admin); headerCreator.addHeader('Content-Type', 'application/json'); @@ -219,12 +255,14 @@ describe('Verify project user/admin access', () => { const mission = await missionRepository.save( missionRepository.create({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access creator: { uuid: globalThis.creator.uuid }, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions name: `test_mission${index + 1}`, project: project, }), ); - return mission['uuid']; + return mission.uuid; }), ); @@ -243,6 +281,7 @@ describe('Verify project user/admin access', () => { // // external test('third party user cannot view any project by default', async () => { // try to get all projects with internal user + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const headerInternal = new HeaderCreator(globalThis.user); headerInternal.addHeader('Content-Type', 'application/json'); @@ -256,6 +295,7 @@ describe('Verify project user/admin access', () => { } // try to get all projects with external user + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const headerExternal = new HeaderCreator(globalThis.externalUser); headerExternal.addHeader('Content-Type', 'application/json'); @@ -270,6 +310,7 @@ describe('Verify project user/admin access', () => { }); test('if external user cannot create a new project', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const headersBuilder = new HeaderCreator(globalThis.externalUser); headersBuilder.addHeader('Content-Type', 'application/json'); @@ -293,6 +334,7 @@ describe('Verify project user/admin access', () => { }); test('if external user cannot delete any project', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const externalHeader = new HeaderCreator(globalThis.externalUser); externalHeader.addHeader('Content-Type', 'application/json'); for (const [, uuid] of globalThis.projectUuids.entries()) { diff --git a/backend/tests/auth/tags/tags-access.test.ts b/backend/tests/auth/tags/tags-access.test.ts index 7078e7f39..60ec7e376 100644 --- a/backend/tests/auth/tags/tags-access.test.ts +++ b/backend/tests/auth/tags/tags-access.test.ts @@ -17,12 +17,14 @@ describe('Verify Project Level Access', () => { // define tests + // eslint-disable-next-line @typescript-eslint/require-await test('if viewer of a project cannot add any tag types', async () => { // TODO: implement this test expect(true).toBe(true); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if viewer of a project cannot add any tag types', async () => { // TODO: implement this test expect(true).toBe(true); @@ -55,6 +57,7 @@ describe('Verify tags/metadata type generation', () => { await database.destroy(); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if internal user can add string tags/metadata', async () => { // TODO: finish test expect(true).toBe(true); @@ -75,6 +78,7 @@ describe('Verify tags/metadata type generation', () => { // }); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if internal user can add number tags/metadata', async () => { // TODO: finish test expect(true).toBe(true); @@ -89,6 +93,7 @@ describe('Verify tags/metadata type generation', () => { // ); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if internal user can add boolean tags/metadata', async () => { // TODO: finish test expect(true).toBe(true); @@ -103,6 +108,7 @@ describe('Verify tags/metadata type generation', () => { // ); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if internal user can add date tags/metadata', async () => { // TODO: finish test expect(true).toBe(true); @@ -115,6 +121,7 @@ describe('Verify tags/metadata type generation', () => { // ); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if internal user can add location tags/metadata', async () => { // TODO: finish test expect(true).toBe(true); @@ -127,6 +134,7 @@ describe('Verify tags/metadata type generation', () => { // ); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if internal user can add link tags/metadata', async () => { // TODO: finish test expect(true).toBe(true); @@ -139,6 +147,7 @@ describe('Verify tags/metadata type generation', () => { // ); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if internal user can add any tags/metadata', async () => { // TODO: finish test expect(true).toBe(true); @@ -151,6 +160,7 @@ describe('Verify tags/metadata type generation', () => { // ); }); + // eslint-disable-next-line @typescript-eslint/require-await test('if internal user can not create metadata with the same name AND datatype', async () => { // TODO: finish test expect(true).toBe(true); diff --git a/backend/tests/auth/utilities.ts b/backend/tests/auth/utilities.ts index 361677f0b..f630ac10f 100644 --- a/backend/tests/auth/utilities.ts +++ b/backend/tests/auth/utilities.ts @@ -1,6 +1,5 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { AccessGroupType, UserRole } from '@common/frontend_shared/enum'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { UserRole } from '@kleinkram/shared'; import { HeaderCreator } from '../utils/api-calls'; import { database, @@ -10,70 +9,7 @@ import { } from '../utils/database-utilities'; // DEFAUL_URL for backend -export const DEFAULT_URL = 'http://localhost:3000'; -export const DEFAULT_GROUP_UUIDS: [string] = [ - '00000000-0000-0000-0000-000000000000', -] as const; -export const getAllAccessGroups = async (): Promise => { - const accessGroupRepository = - database.getRepository(AccessGroupEntity); - return (await accessGroupRepository.find({ - relations: ['members', 'members.user'], - select: { - memberships: { - uuid: true, - user: { - uuid: true, - name: true, - email: true, - role: true, - }, - }, - }, - })) as AccessGroupEntity[]; -}; - -/** - * Verify if an access group with the passed uuid exists - * - * @param uuid uuid of the access group to search for - * @param accessGroups list of access groups to search in - */ -export const verifyIfGroupWithUUIDExists = ( - uuid: string, - accessGroups: AccessGroupEntity[], -) => { - const group = accessGroups.filter( - (_group: AccessGroupEntity) => _group.uuid === uuid, - ); - expect(group.length).toBe(1); -}; -/** - * Get the personal access group of the user with the passed email - * - * @param email email of the user - * @param accessGroups list of access groups - * - * @returns access group of the user - */ -export const getAccessGroupForEmail = ( - email: string, - accessGroups: AccessGroupEntity[], -): AccessGroupEntity => { - const group: AccessGroupEntity[] = - accessGroups.filter((_group: AccessGroupEntity) => - _group.memberships?.some( - (accessGroupUser) => - accessGroupUser.user?.email === email && - _group.type === AccessGroupType.PRIMARY, - ), - ) ?? []; - - const thereIsOnlyOnePersonalGroup = group.length === 1; - expect(thereIsOnlyOnePersonalGroup).toBe(true); - - return group[0] as unknown as AccessGroupEntity; -}; +export const DEFAULT_URL = process.env.DEFAULT_URL ?? 'http://localhost:3000'; /** * Generates a user in the database, retrieves user details, and fetches a response. @@ -93,7 +29,7 @@ export const generateAndFetchDatabaseUser = async ( const baseEmail = userType === 'internal' - ? 'internal-user@kleinkram.leggedrobotics.com' + ? 'internal-user@kleinkram.dev' : 'external-user@third-party.com'; let userEmail = baseEmail; @@ -104,6 +40,7 @@ export const generateAndFetchDatabaseUser = async ( const userRepository = database.getRepository(UserEntity); // Check if user already exists and find an available email and username + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { try { await userRepository.findOneOrFail({ @@ -111,7 +48,9 @@ export const generateAndFetchDatabaseUser = async ( }); // If the user exists, modify the email and username const [name, domain] = baseEmail.split('@'); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions userEmail = `${name}${counter}@${domain}`; + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions username = `${name}${counter}`; counter++; } catch { @@ -137,6 +76,7 @@ export const generateAndFetchDatabaseUser = async ( if (!response.ok) { throw new Error( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Failed to fetch data: ${response.status} ${response.statusText}`, ); } diff --git a/backend/tests/files/file-crud.test.ts b/backend/tests/files/file-crud.test.ts new file mode 100644 index 000000000..a3ba0e848 --- /dev/null +++ b/backend/tests/files/file-crud.test.ts @@ -0,0 +1,174 @@ +import { FileEntity } from '@kleinkram/backend-common'; +import { FileState, UserRole } from '@kleinkram/shared'; +import { DEFAULT_URL } from '../auth/utilities'; +import { + createMissionUsingPost, + getAuthHeaders, + uploadFile, +} from '../utils/api-calls'; +import { database } from '../utils/database-utilities'; +import { + setupDatabaseHooks, + setupTestEnvironment, +} from '../utils/test-helpers'; + +describe('File Management Tests', () => { + setupDatabaseHooks(); + + test('should upload and download a file', async () => { + const { user, missionUuid } = await setupTestEnvironment( + 'test-file@kleinkram.dev', + 'File User', + UserRole.ADMIN, + ); + + // Upload + // Note: uploadFile utility mocks the multipart upload process + await uploadFile(user, 'test.bag', missionUuid); + + // Verify file exists in DB + const fileRepo = database.getRepository(FileEntity); + const file = await fileRepo.findOne({ + where: { filename: 'test.bag' }, + relations: ['mission'], + }); + expect(file).not.toBeNull(); + expect(file?.mission?.uuid).toBe(missionUuid); + + expect(file?.state).toBe(FileState.OK); + + // Download + const downloadResponse = await fetch( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `${DEFAULT_URL}/files/download?uuid=${file?.uuid}&expires=false&preview_only=false`, + { + method: 'GET', + headers: getAuthHeaders(user), + }, + ); + expect(downloadResponse.status).toBeLessThan(300); + }, 30_000); + + test('should delete a file', async () => { + const { user, missionUuid } = await setupTestEnvironment( + 'test-delete@kleinkram.dev', + 'Delete User', + UserRole.ADMIN, + ); + + await uploadFile(user, 'to_delete.bag', missionUuid); + const fileRepo = database.getRepository(FileEntity); + const file = await fileRepo.findOneOrFail({ + where: { filename: 'to_delete.bag' }, + }); + + const deleteResponse = await fetch( + `${DEFAULT_URL}/files/${file.uuid}`, + { + method: 'DELETE', + headers: getAuthHeaders(user), + }, + ); + expect(deleteResponse.status).toBeLessThan(300); + + const deletedFile = await fileRepo.findOne({ + where: { uuid: file.uuid }, + }); + expect(deletedFile).toBeNull(); + }, 30_000); + + test('should delete multiple files', async () => { + const { user, missionUuid } = await setupTestEnvironment( + 'test-multi-delete@kleinkram.dev', + 'Multi Delete User', + UserRole.ADMIN, + ); + + await uploadFile(user, 'file1.bag', missionUuid); + await uploadFile(user, 'file2.bag', missionUuid); + + const fileRepo = database.getRepository(FileEntity); + const file1 = await fileRepo.findOneOrFail({ + where: { filename: 'file1.bag' }, + }); + const file2 = await fileRepo.findOneOrFail({ + where: { filename: 'file2.bag' }, + }); + + const deleteResponse = await fetch( + `${DEFAULT_URL}/files/deleteMultiple`, + { + method: 'POST', + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Content-Type': 'application/json', + ...getAuthHeaders(user), + }, + body: JSON.stringify({ + uuids: [file1.uuid, file2.uuid], + missionUUID: missionUuid, + }), + }, + ); + expect(deleteResponse.status).toBeLessThan(300); + + const deletedFile1 = await fileRepo.findOne({ + where: { uuid: file1.uuid }, + }); + const deletedFile2 = await fileRepo.findOne({ + where: { uuid: file2.uuid }, + }); + expect(deletedFile1).toBeNull(); + expect(deletedFile2).toBeNull(); + }, 30_000); + + test('should move file to another mission', async () => { + const { user, missionUuid, projectUuid } = await setupTestEnvironment( + 'test-move@kleinkram.dev', + 'Move User', + UserRole.ADMIN, + ); + + const mission1Uuid = missionUuid; // Renaming for clarity, as missionUuid from setupTestEnvironment is the first mission + const mission2Uuid = await createMissionUsingPost( + { + name: 'mission_2', + projectUUID: projectUuid, + tags: {}, + ignoreTags: true, + }, + user, + ); + + await uploadFile(user, 'move_me.bag', mission1Uuid); + const fileRepo = database.getRepository(FileEntity); + const file = await fileRepo.findOneOrFail({ + where: { filename: 'move_me.bag' }, + relations: ['mission'], + }); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + expect(file?.mission?.uuid).toBe(mission1Uuid); + + const moveResponse = await fetch(`${DEFAULT_URL}/files/moveFiles`, { + method: 'POST', + + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Content-Type': 'application/json', + ...getAuthHeaders(user), + }, + body: JSON.stringify({ + fileUUIDs: [file.uuid], + missionUUID: mission2Uuid, + }), + }); + expect(moveResponse.status).toBeLessThan(300); + + const movedFile = await fileRepo.findOneOrFail({ + where: { uuid: file.uuid }, + relations: ['mission'], + }); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + expect(movedFile?.mission?.uuid).toBe(mission2Uuid); + }, 30_000); +}); diff --git a/backend/tests/files/topic-extraction.test.ts b/backend/tests/files/topic-extraction.test.ts new file mode 100644 index 000000000..0acc54c12 --- /dev/null +++ b/backend/tests/files/topic-extraction.test.ts @@ -0,0 +1,70 @@ +import { FileEntity, TopicEntity } from '@kleinkram/backend-common'; +import { DEFAULT_URL } from '../auth/utilities'; +import { getAuthHeaders, uploadFile } from '../utils/api-calls'; +import { database } from '../utils/database-utilities'; +import { + setupDatabaseHooks, + setupTestEnvironment, +} from '../utils/test-helpers'; + +describe('Topic Extraction Tests', () => { + jest.setTimeout(60_000); + setupDatabaseHooks(); + + test('should extract topics from a file', async () => { + console.log('[DEBUG] Starting test setup'); + const { user, missionUuid } = await setupTestEnvironment( + 'test-topic@kleinkram.dev', + 'Topic User', + ); + console.log('[DEBUG] Test setup complete'); + + console.log('[DEBUG] Uploading file'); + await uploadFile(user, 'test.bag', missionUuid); + console.log('[DEBUG] File uploaded'); + + const fileRepo = database.getRepository(FileEntity); + const file = await fileRepo.findOneOrFail({ + where: { filename: 'test.bag' }, + }); + console.log('[DEBUG] File found in DB'); + + const topicRepo = database.getRepository(TopicEntity); + const topic = topicRepo.create({ + name: '/test/topic', + type: 'std_msgs/String', + file: file, + nrMessages: BigInt(100), + frequency: 10, + messageEncoding: 'none', + }); + await topicRepo.save(topic); + console.log('[DEBUG] Topic saved'); + + // Verify topics via API + // GET /topic/all + console.log('[DEBUG] Fetching topics via API'); + const response = await fetch( + `${DEFAULT_URL}/topic/all?skip=0&take=10`, + { + method: 'GET', + headers: getAuthHeaders(user), + }, + ); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + console.log(`[DEBUG] API response status: ${response.status}`); + expect(response.status).toBe(200); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const json = await response.json(); + console.log('[DEBUG] API response json received'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(json.data).toBeDefined(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(json.data.length).toBeGreaterThan(0); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + const foundTopic = json.data.find((t: any) => t.name === '/test/topic'); + expect(foundTopic).toBeDefined(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(foundTopic.type).toBe('std_msgs/String'); + }, 30_000); +}); diff --git a/backend/tests/files/upload-confirmation.test.ts b/backend/tests/files/upload-confirmation.test.ts new file mode 100644 index 000000000..e9de9e629 --- /dev/null +++ b/backend/tests/files/upload-confirmation.test.ts @@ -0,0 +1,190 @@ +import { ProjectEntity } from '@kleinkram/backend-common'; + +jest.mock('uuid', () => ({ + v4: () => 'test-uuid', +})); + +import { + FileEntity, + IngestionJobEntity, + MissionEntity, + UserEntity, +} from '@kleinkram/backend-common'; +import { + FileEventType, + FileOrigin, + FileState, + FileType, +} from '@kleinkram/shared'; +import { Repository } from 'typeorm'; +import QueueService from '../../src/services/queue.service'; +import { + clearAllData, + database, + getUserFromDatabase, + mockDatabaseUser, +} from '../utils/database-utilities'; + +// Mock dependencies +const mockStorageService = { + getFileInfo: jest.fn().mockResolvedValue({ size: 1024 }), +}; + +const mockFileAuditService = { + log: jest.fn(), +}; + +const mockGauge = { + set: jest.fn(), +}; + +describe('Reproduction Issue: File Hash Not Saved', () => { + let queueService: QueueService; + let fileRepository: Repository; + let queueRepository: Repository; + let missionRepository: Repository; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let userRepository: Repository; + let user: UserEntity; + let missionUuid: string; + + beforeAll(async () => { + await database.initialize(); + }); + + afterAll(async () => { + await database.destroy(); + }); + + beforeEach(async () => { + await clearAllData(); + + fileRepository = database.getRepository(FileEntity); + queueRepository = database.getRepository(IngestionJobEntity); + missionRepository = database.getRepository(MissionEntity); + userRepository = database.getRepository(UserEntity); + + // Setup Data + const userUuid = await mockDatabaseUser('test@example.com'); + user = await getUserFromDatabase(userUuid); + + const project = await database.getRepository(ProjectEntity).save({ + name: 'Test Project', + description: 'Test Description', + creator: user, + }); + + const mission = await missionRepository.save({ + name: 'Test Mission', + project: project, + creator: user, + date: new Date(), + }); + missionUuid = mission.uuid; + + // Instantiate QueueService manually + queueService = new QueueService( + queueRepository, + missionRepository, + fileRepository, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + { findOneByUUID: jest.fn() } as any, // UserService + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + mockStorageService as any, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + mockFileAuditService as any, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + mockGauge as any, // pendingJobs + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + mockGauge as any, // activeJobs + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + mockGauge as any, // completedJobs + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + mockGauge as any, // failedJobs + ); + + // Mock the fileQueue property + Object.defineProperty(queueService, 'fileQueue', { + value: { + add: jest.fn().mockResolvedValue({}), + }, + writable: true, + }); + }); + + it('should save the file hash and source when confirming upload', async () => { + // 1. Create a file in UPLOADING state + const file = await fileRepository.save({ + filename: 'test.bag', + mission: { uuid: missionUuid }, + creator: user, + type: FileType.BAG, + state: FileState.UPLOADING, + origin: FileOrigin.UPLOAD, + date: new Date(), + size: 0, + }); + + const testHash = 'd41d8cd98f00b204e9800998ecf8427e'; // MD5 for empty string + const testSource = 'CLI'; + + // 2. Call confirmUpload with source + await queueService.confirmUpload(file.uuid, testHash, user, testSource); + + // 3. Verify file hash + const updatedFile = await fileRepository.findOne({ + where: { uuid: file.uuid }, + }); + + expect(updatedFile).toBeDefined(); + expect(updatedFile?.state).toBe(FileState.OK); + expect(updatedFile?.hash).toBe(testHash); + + // 4. Verify file event + expect(mockFileAuditService.log).toHaveBeenCalledWith( + FileEventType.UPLOAD_COMPLETED, + expect.objectContaining({ + fileUuid: file.uuid, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + details: expect.objectContaining({ + origin: FileOrigin.UPLOAD, + source: testSource, + }), + }), + true, + ); + }); + + it('should default source to Web Interface if not provided', async () => { + // 1. Create a file in UPLOADING state + const file = await fileRepository.save({ + filename: 'test_default.bag', + mission: { uuid: missionUuid }, + creator: user, + type: FileType.BAG, + state: FileState.UPLOADING, + origin: FileOrigin.UPLOAD, + date: new Date(), + size: 0, + }); + + const testHash = 'd41d8cd98f00b204e9800998ecf8427e'; + + // 2. Call confirmUpload without source (should default) + await queueService.confirmUpload(file.uuid, testHash, user); + + // 3. Verify file event + expect(mockFileAuditService.log).toHaveBeenCalledWith( + FileEventType.UPLOAD_COMPLETED, + expect.objectContaining({ + fileUuid: file.uuid, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + details: expect.objectContaining({ + origin: FileOrigin.UPLOAD, + source: 'Web Interface', + }), + }), + true, + ); + }); +}); diff --git a/backend/tests/fixtures/test_small.bag b/backend/tests/fixtures/test_small.bag deleted file mode 100644 index 15d6df3c9..000000000 Binary files a/backend/tests/fixtures/test_small.bag and /dev/null differ diff --git a/backend/tests/fixtures/test_small.mcap b/backend/tests/fixtures/test_small.mcap deleted file mode 100644 index 3e9cdbe65..000000000 Binary files a/backend/tests/fixtures/test_small.mcap and /dev/null differ diff --git a/backend/tests/global.d.ts b/backend/tests/global.d.ts new file mode 100644 index 000000000..0b999fd73 --- /dev/null +++ b/backend/tests/global.d.ts @@ -0,0 +1,22 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare global { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + interface GlobalThis { + [key: string]: any; + } + + var creator: any; + var user: any; + var externalUser: any; + var admin: any; + var userToken: string; + var userResponse: any; + var projectUuid: string; + var missionUuid: string; + var templateUuid: string; + var metadataUuid: string; + var projectUuids: string[]; + var tagName: string; +} + +export {}; diff --git a/backend/tests/utils/api-calls.ts b/backend/tests/utils/api-calls.ts index ee7fa62c0..2354e4597 100644 --- a/backend/tests/utils/api-calls.ts +++ b/backend/tests/utils/api-calls.ts @@ -1,20 +1,29 @@ +import { appVersion } from '@/app-version'; import { S3Client } from '@aws-sdk/client-s3'; -import { CreateAccessGroupDto } from '@common/api/types/create-access-group.dto'; -import { CreateMission } from '@common/api/types/create-mission.dto'; -import { CreateProject } from '@common/api/types/create-project.dto'; -import { CreateTemplateDto } from '@common/api/types/create-template.dto'; -import { CreateTagTypeDto } from '@common/api/types/tags/create-tag-type.dto'; -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import IngestionJobEntity from '@common/entities/file/ingestion-job.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { QueueState } from '@common/frontend_shared/enum'; +import { CreateTemplateDto } from '@kleinkram/api-dto/types/actions/create-template.dto'; +import { CreateAccessGroupDto } from '@kleinkram/api-dto/types/create-access-group.dto'; +import { CreateMission } from '@kleinkram/api-dto/types/create-mission.dto'; +import { CreateProject } from '@kleinkram/api-dto/types/create-project.dto'; +import { CreateTagTypeDto } from '@kleinkram/api-dto/types/tags/create-tag-type.dto'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; import crypto from 'node:crypto'; import * as fs from 'node:fs'; -import { appVersion } from '../../src/app-version'; import { DEFAULT_URL } from '../auth/utilities'; import { database, getJwtToken } from './database-utilities'; import { uploadFileMultipart } from './multipart-upload'; +export const getAuthHeaders = (user?: UserEntity): Record => { + const headers: Record = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kleinkram-client-version': appVersion, + }; + if (user) { + headers.cookie = `authtoken=${getJwtToken(user)}`; + } + return headers; +}; + export class HeaderCreator { headers: Headers; @@ -77,9 +86,11 @@ export const createProjectUsingPost = async ( credentials: 'include', }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await response.json(); console.log(`['DEBUG'] Created project:`, json); expect(response.status).toBeLessThan(300); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return json.uuid; }; @@ -104,8 +115,10 @@ export const createMissionUsingPost = async ( // check if the request was successful expect(response.status).toBeLessThan(300); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await response.json(); console.log(`['DEBUG'] Created mission:`, json); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return json.uuid; }; @@ -126,11 +139,17 @@ export async function uploadFile( filename: string, missionUuid: string, ): Promise { - const response = await fetch(`${DEFAULT_URL}/file/temporaryAccess`, { + const response = await fetch(`${DEFAULT_URL}/files/temporaryAccess`, { method: 'POST', + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention 'Content-Type': 'application/json', + + // eslint-disable-next-line @typescript-eslint/await-thenable cookie: `authtoken=${await getJwtToken(user)}`, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kleinkram-client-version': appVersion, }, body: JSON.stringify({ filenames: [filename], @@ -139,17 +158,28 @@ export async function uploadFile( }); expect(response.status).toBe(201); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await response.json(); expect(json).toBeDefined(); - const fileresponseponse = json[0]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const fileresponseponse = json.data[0]; expect(fileresponseponse).toBeDefined(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect(fileresponseponse.bucket).toBe('data'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect(fileresponseponse.fileUUID).toBeDefined(); - // open file from fixturesponse - const file = fs.readFileSync(`./tests/fixturesponse/${filename}`); + // open file from fixtures + const filePath = `./tests/fixtures/${filename}`; + if (!fs.existsSync(filePath)) { + throw new Error( + `Test data file '${filename}' not found at '${filePath}'. ` + + `Please run 'python3 cli/tests/generate_test_data.py' to generate the required test data.`, + ); + } + const file = fs.readFileSync(filePath); const blob = new Blob([file], { type: 'application/octet-stream', }); @@ -163,15 +193,20 @@ export async function uploadFile( forcePathStyle: true, region: 'us-east-1', credentials: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access accessKeyId: fileresponseponse.accessCredentials.accessKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access secretAccessKey: fileresponseponse.accessCredentials.secretKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access sessionToken: fileresponseponse.accessCredentials.sessionToken, }, }); const responsei = await uploadFileMultipart( fileFile, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access fileresponseponse.bucket, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access fileresponseponse.fileUUID, minioClient, ); @@ -179,41 +214,25 @@ export async function uploadFile( // confirm upload // http://localhost:3000/queue/confirmUpload - const responseConfirm = await fetch(`${DEFAULT_URL}/queue/confirmUpload`, { + + const responseConfirm = await fetch(`${DEFAULT_URL}/files/upload/confirm`, { method: 'POST', headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention 'Content-Type': 'application/json', + // eslint-disable-next-line @typescript-eslint/await-thenable cookie: `authtoken=${await getJwtToken(user)}`, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kleinkram-client-version': appVersion, }, body: JSON.stringify({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access uuid: fileresponseponse.fileUUID, md5: hash.digest('base64'), }), }); expect(responseConfirm.status).toBe(201); - while (true) { - const responseActive = await fetch(`${DEFAULT_URL}/queue/active`, { - method: 'GET', - headers: { - cookie: `authtoken=${await getJwtToken(user)}`, - }, - }); - - expect(responseActive.status).toBe(200); - const active = await responseActive.json(); - if ( - active.some( - (x: IngestionJobEntity) => - x.uuid === fileresponseponse.queueUUID && - x.state === QueueState.COMPLETED, - ) - ) { - break; - } - await new Promise((r) => setTimeout(r, 1000)); - } - return fileHash; } @@ -233,9 +252,11 @@ export const createMetadataUsingPost = async ( }), }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await response.json(); console.log(`['DEBUG'] Created tag:`, json); expect(response.status).toBeLessThan(300); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return json.uuid; }; @@ -257,13 +278,14 @@ export const createAccessGroupUsingPost = async ( }); // get access group - const testAccesGroup = await database + const testAccessGroup = await database .getRepository('AccessGroup') .findOneOrFail({ where: { name: accessGroup.name } }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const groupJson = await response.json(); expect(response.status).toBeLessThan(300); - console.log(`['DEBUG'] Created access group:`, testAccesGroup.uuid); + console.log(`['DEBUG'] Created access group:`, testAccessGroup.uuid); // add users to access group await Promise.all( @@ -275,11 +297,12 @@ export const createAccessGroupUsingPost = async ( headers: headersBuilder.getHeaders(), body: JSON.stringify({ userUUID: user.uuid, - uuid: testAccesGroup.uuid, + uuid: testAccessGroup.uuid, }), }, ); expect(groupResponse.status).toBeLessThan(300); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await groupResponse.json(); console.log( `['DEBUG'] Added user ${user.uuid} to access group:`, @@ -290,7 +313,7 @@ export const createAccessGroupUsingPost = async ( console.log(`['DEBUG'] Users added to access group:`, groupJson); - return testAccesGroup.uuid; + return testAccessGroup.uuid; }; export const createActionUsingPost = async ( @@ -301,15 +324,17 @@ export const createActionUsingPost = async ( const headersBuilder = new HeaderCreator(user); headersBuilder.addHeader('Content-Type', 'application/json'); - const response = await fetch(`${DEFAULT_URL}/action/createTemplate`, { + const response = await fetch(`${DEFAULT_URL}/templates`, { method: 'POST', headers: headersBuilder.getHeaders(), body: JSON.stringify(action), credentials: 'include', }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = await response.json(); console.log(`['DEBUG'] Created action:`, json); expect(response.status).toBeLessThan(300); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return json.uuid; }; diff --git a/backend/tests/utils/database-utilities.ts b/backend/tests/utils/database-utilities.ts index d2bebcd72..d009f2148 100644 --- a/backend/tests/utils/database-utilities.ts +++ b/backend/tests/utils/database-utilities.ts @@ -1,33 +1,33 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import GroupMembershipEntity from '@common/entities/auth/group-membership.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { Providers, UserRole } from '@common/frontend_shared/enum'; +import { + AccessGroupEntity, + AccountEntity, + AffiliationGroupService, + ALL_ENTITIES, + GroupMembershipEntity, + UserEntity, +} from '@kleinkram/backend-common'; +import { Providers, UserRole } from '@kleinkram/shared'; import jwt from 'jsonwebtoken'; import * as crypto from 'node:crypto'; import * as fs from 'node:fs'; + +import { createNewUser } from '@/services/auth.service'; +import path from 'node:path'; import process from 'node:process'; import { DataSource } from 'typeorm'; -import { - createAccessGroups, - createNewUser, -} from '../../src/services/auth.service'; -const databasePort = process.env['DB_PORT']; +const databasePort = process.env.DB_PORT; export const database = new DataSource({ type: 'postgres', host: 'localhost', port: Number.parseInt(databasePort ?? '5432', 10), - ssl: process.env['DB_SSL'] === 'true', - username: process.env['DB_USER'] ?? '', - password: process.env['DB_PASSWORD'] ?? '', - database: process.env['DB_DATABASE'] ?? '', + ssl: process.env.DB_SSL === 'true', + username: process.env.DB_USER ?? '', + password: process.env.DB_PASSWORD ?? '', + database: process.env.DB_DATABASE ?? '', synchronize: false, - entities: [ - '../common/entities/**/*.entity{.ts,.js}', - '../common/viewEntities/**/*.entity{.ts,.js}', - ], + entities: ALL_ENTITIES, }); export const clearAllData = async () => { @@ -36,6 +36,7 @@ export const clearAllData = async () => { // filter out the tables that should not be cleared (e.g. views) const tablesToClear = entities + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition .filter((entity) => entity.tableName !== undefined) .filter((entity) => !entity.tableName.includes('view')) .filter((entity) => !entity.tableName.includes('materialized')) @@ -44,7 +45,9 @@ export const clearAllData = async () => { .join(', '); await database.query(`TRUNCATE ${tablesToClear} CASCADE;`); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access throw new Error(`ERROR: Cleaning test database: ${error.toString()}`); } }; @@ -55,13 +58,20 @@ export const mockDatabaseUser = async ( role: UserRole = UserRole.USER, ): Promise => { // read config from access_config.json - - const config = JSON.parse(fs.readFileSync('access_config.json', 'utf8')); + // eslint-disable-next-line unicorn/prefer-module + const configPath = path.join(__dirname, '../../src/access_config.json'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); const accessGroupRepository = database.getRepository(AccessGroupEntity); const groupMembershipRepository = database.getRepository( GroupMembershipEntity, ); - await createAccessGroups(accessGroupRepository, config); + const affiliationGroupService = new AffiliationGroupService( + accessGroupRepository, + groupMembershipRepository, + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await affiliationGroupService.createAccessGroups(config); const userRepository = database.getRepository(UserEntity); const accountRepository = database.getRepository(AccountEntity); @@ -72,11 +82,11 @@ export const mockDatabaseUser = async ( const oauthID = hash.digest('hex'); await createNewUser( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument config, userRepository, accountRepository, - accessGroupRepository, - groupMembershipRepository, + affiliationGroupService, { oauthID, provider: Providers.GOOGLE, @@ -86,6 +96,7 @@ export const mockDatabaseUser = async ( }, ); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (role) { const user = await userRepository.findOneOrFail({ where: { email: email }, @@ -102,7 +113,7 @@ export const mockDatabaseUser = async ( }; export const getJwtToken = (user: UserEntity): string => { - const jwtSecret = process.env['JWT_SECRET']; + const jwtSecret = process.env.JWT_SECRET; if (!jwtSecret) { throw new Error( 'JWT_SECRET is not defined in the environment variables.', diff --git a/backend/tests/utils/endpoints.ts b/backend/tests/utils/endpoints.ts index c5ede5d89..d52c1d454 100644 --- a/backend/tests/utils/endpoints.ts +++ b/backend/tests/utils/endpoints.ts @@ -14,5 +14,6 @@ export const getEndpoints = (): { '.endpoints/__generated__endpoints.json', 'utf8', ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return JSON.parse(fileContent); }; diff --git a/backend/tests/utils/multipart-upload.ts b/backend/tests/utils/multipart-upload.ts index 7d2c9f261..0c1fd2d37 100644 --- a/backend/tests/utils/multipart-upload.ts +++ b/backend/tests/utils/multipart-upload.ts @@ -28,6 +28,7 @@ export async function uploadFileMultipart( // Step 2: Upload Parts const partSize = 50 * 1024 * 1024; // 5 MB per part (adjust as needed) + // eslint-disable-next-line @typescript-eslint/no-explicit-any const parts: any[] = []; for ( let partNumber = 1, start = 0; @@ -35,6 +36,7 @@ export async function uploadFileMultipart( partNumber++, start += partSize ) { const end = Math.min(start + partSize, file.length); + // eslint-disable-next-line @typescript-eslint/no-deprecated const partBlob = file.slice(start, end); const uploadPartCommand = new UploadPartCommand({ Bucket: bucket, diff --git a/backend/tests/utils/test-helpers.ts b/backend/tests/utils/test-helpers.ts new file mode 100644 index 000000000..c3088e13a --- /dev/null +++ b/backend/tests/utils/test-helpers.ts @@ -0,0 +1,63 @@ +import { WorkerEntity } from '@kleinkram/backend-common/entities/worker/worker.entity'; +import { UserRole } from '@kleinkram/shared'; +import { createMissionUsingPost, createProjectUsingPost } from './api-calls'; +import { + clearAllData, + database, + getUserFromDatabase, + mockDatabaseUser, +} from './database-utilities'; + +export const setupDatabaseHooks = () => { + beforeAll(async () => { + await database.initialize(); + await clearAllData(); + }, 30_000); + + beforeEach(clearAllData); + + afterAll(async () => { + await database.destroy(); + }); +}; + +export const setupTestEnvironment = async ( + email = 'test@kleinkram.dev', + username = 'Test Env User', + role = UserRole.ADMIN, +) => { + const userId = await mockDatabaseUser(email, username, role); + const user = await getUserFromDatabase(userId); + const projectUuid = await createProjectUsingPost( + { name: 'test_project', description: 'desc', requiredTags: [] }, + user, + ); + const missionUuid = await createMissionUsingPost( + { + name: 'test_mission', + projectUUID: projectUuid, + tags: {}, + ignoreTags: true, + }, + user, + ); + return { user, projectUuid, missionUuid }; +}; + +export const createMockWorker = async (identifier = 'test-worker') => { + const workerRepo = database.getRepository(WorkerEntity); + const worker = workerRepo.create({ + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + identifier: `${identifier}-${Date.now()}`, + hostname: 'test-host', + cpuCores: 8, + cpuMemory: 16, + gpuMemory: 0, + cpuModel: 'Test CPU', + storage: 1000, + lastSeen: new Date(), + reachable: true, + }); + await workerRepo.save(worker); + return worker; +}; diff --git a/backend/tests/test-utilities.test.ts b/backend/tests/utils/test-utilities.test.ts similarity index 74% rename from backend/tests/test-utilities.test.ts rename to backend/tests/utils/test-utilities.test.ts index 16e88e681..433437836 100644 --- a/backend/tests/test-utilities.test.ts +++ b/backend/tests/utils/test-utilities.test.ts @@ -1,12 +1,8 @@ -import AccessGroupEntity from '@common/entities/auth/accessgroup.entity'; -import AccountEntity from '@common/entities/auth/account.entity'; -import UserEntity from '@common/entities/user/user.entity'; -import { UserRole } from '@common/frontend_shared/enum'; -import { - clearAllData, - database, - mockDatabaseUser, -} from './utils/database-utilities'; +import { AccessGroupEntity } from '@kleinkram/backend-common'; +import { AccountEntity } from '@kleinkram/backend-common/entities/auth/account.entity'; +import { UserEntity } from '@kleinkram/backend-common/entities/user/user.entity'; +import { UserRole } from '@kleinkram/shared'; +import { clearAllData, database, mockDatabaseUser } from './database-utilities'; describe('Test Suite Utils', () => { beforeAll(async () => { @@ -24,7 +20,7 @@ describe('Test Suite Utils', () => { // Insert some data const user = new UserEntity(); user.name = 'John Doe'; - user.email = 'test-01@kleinkram.leggedrobotics.com'; + user.email = 'test-01@kleinkram.dev'; user.role = UserRole.USER; await database.getRepository(UserEntity).save(user); @@ -44,14 +40,14 @@ describe('Test Suite Utils', () => { test('Create User with Valid Token', async () => { // TODO: Finish this test - await mockDatabaseUser('test-01@kleinkram.leggedrobotics.com'); + await mockDatabaseUser('test-01@kleinkram.dev'); const userRepository = database.getRepository(UserEntity); const users = await userRepository.find({ select: ['email', 'uuid'], }); expect(users.length).toBe(1); - expect(users[0]?.email).toBe('test-01@kleinkram.leggedrobotics.com'); + expect(users[0]?.email).toBe('test-01@kleinkram.dev'); const accountRepository = database.getRepository(AccountEntity); const accounts = await accountRepository.find(); diff --git a/backend/tests/verifiy-file-handling.test.ts b/backend/tests/verifiy-file-handling.test.ts deleted file mode 100644 index f9d9f9148..000000000 --- a/backend/tests/verifiy-file-handling.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { clearAllData, database } from './utils/database-utilities'; - -describe('Verify File Handling', () => { - beforeAll(async () => { - await database.initialize(); - await clearAllData(); - }); - - beforeEach(clearAllData); - afterAll(async () => { - await database.destroy(); - }); - - test('Test if file is uploaded and can be downloaded again', async () => { - // TODO: implement this test - expect(true).toBe(true); - - // const filename = 'test_small.bag'; - - // const userId = await mockDatabaseUser('internal@kleinkram.leggedrobotics.com'); - // const user = await getUserFromDatabase(userId); - - // // create project - // const projectUuid = await createProjectUsingPost( - // { - // name: 'test_project', - // description: 'test description', - // requiredTags: [], - // }, - // user, - // ); - // expect(projectUuid).toBeDefined(); - - // // create mission using the post - // const missionUuid = await createMissionUsingPost( - // { - // name: 'test_mission2', - // projectUUID: projectUuid, - // tags: {}, - // ignoreTags: true, - // }, - // user, - // ); - // expect(missionUuid).toBeDefined(); - // const fileHash = await uploadFile(user, filename, missionUuid); - - // // get file uuid by calling /file/oneByName - // const resOneByName = await fetch( - // `http://localhost:3000/file/oneByName?filename=${filename}&uuid=${missionUuid}`, - // { - // method: 'GET', - // headers: { - // cookie: `authtoken=${await getJwtToken(user)}`, - // }, - // }, - // ); - - // expect(resOneByName.status).toBe(200); - // const jsonOneByName = await resOneByName.json(); - // expect(jsonOneByName).toBeDefined(); - // expect(jsonOneByName.uuid).toBeDefined(); - - // // download file - // // http://localhost:3000/file/download?uuid={uuid} - // const resDownload = await fetch( - // `http://localhost:3000/file/download?uuid=${jsonOneByName.uuid}&expires=false`, - // { - // method: 'GET', - // headers: { - // cookie: `authtoken=${await getJwtToken(user)}`, - // }, - // }, - // ); - - // expect(resDownload.status).toBe(200); - // const downloadFile = await resDownload.blob(); - - // // verify file content - // const downloadFileHash = await crypto.subtle.digest( - // 'SHA-256', - // await downloadFile.arrayBuffer(), - // ); - - // expect(downloadFileHash).toEqual(fileHash); - }, 30_000); - - test('Test if two files can be uploaded and downloaded in parallel', () => { - // TODO: implement this test - expect(true).toBe(true); - }); - - test('if two files with the same name cannot be uploaded to the same mission', () => { - // TODO: implement this test - expect(true).toBe(true); - }); - - test('if project can be renamed while uploading a file', () => { - // TODO: implement this test - expect(true).toBe(true); - }); - - test('if mission can be renamed while uploading a file', () => { - // TODO: implement this test - expect(true).toBe(true); - }); - - test('if you cannot upload file with arbitrary name', () => { - // TODO: implement this test - expect(true).toBe(true); - }); - - test('if you cannot upload file to arbitrary bucket', () => { - // TODO: implement this test - expect(true).toBe(true); - }); - - test('if you can abort the upload, then retry', () => { - // TODO: implement this test - expect(true).toBe(true); - }); - - test('if you cannot download a file you should not have access to', () => { - // TODO: implement this test - expect(true).toBe(true); - }); -}); diff --git a/backend/tsconfig.build.json b/backend/tsconfig.build.json index 37afa2cac..7354c6cb4 100644 --- a/backend/tsconfig.build.json +++ b/backend/tsconfig.build.json @@ -1,4 +1,10 @@ { "extends": "./tsconfig.json", - "exclude": ["node_modules", "tests", "dist", "**/*spec.ts"] + "exclude": ["node_modules", "tests", "dist", "**/*spec.ts"], + "include": ["src/**/*", "../packages/**/*", "src/access_config.json"], + "compilerOptions": { + "baseUrl": ".", + "rootDir": ".." + }, + "references": [] } diff --git a/backend/tsconfig.dev.json b/backend/tsconfig.dev.json new file mode 100644 index 000000000..028862fee --- /dev/null +++ b/backend/tsconfig.dev.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "composite": false, + "declaration": false, + "outDir": "./dist" + }, + "include": ["src/**/*", "../packages/**/*"], + "references": [] +} diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 797e15ee2..6f06d3ea2 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,31 +1,48 @@ { + "extends": "../tsconfig.base.json", "compilerOptions": { - "strict": true, - "strictNullChecks": true, - "exactOptionalPropertyTypes": true, - "noPropertyAccessFromIndexSignature": true, - "noUncheckedIndexedAccess": true, - "skipLibCheck": true, - "module": "commonjs", - "declaration": true, - "esModuleInterop": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true, - "target": "ES2021", - "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", - "incremental": true, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false, + "outDir": "dist", + "baseUrl": ".", + "rootDir": "..", "paths": { - "@common/*": ["../common/*"] - } + "@/*": ["src/*"], + "@kleinkram/backend-common/*": ["../packages/backend-common/src/*"], + "@kleinkram/shared": ["../packages/shared/src/index.ts"], + "@kleinkram/shared/*": ["../packages/shared/src/*"], + "@kleinkram/validation": ["../packages/validation/src/index.ts"], + "@kleinkram/validation/*": ["../packages/validation/src/*"], + "@kleinkram/api-dto": ["../packages/api-dto/src/index.ts"], + "@kleinkram/api-dto/*": ["../packages/api-dto/src/*"], + "@api-dto/*": ["../packages/api-dto/src/types/*"], + "@kleinkram/backend-common": [ + "../packages/backend-common/src/index.ts" + ], + "@backend-common/*": ["../packages/backend-common/src/*"] + }, + "resolveJsonModule": true }, - "exclude": ["dist/**/*"] + "include": [ + "src/**/*", + "src/**/*.json", + "../packages/**/*", + "check-version.ts", + "migration/**/*.ts", + "tests/**/*.ts", + "scripts/**/*.ts" + ], + "exclude": ["node_modules", "dist"], + "references": [ + { + "path": "../packages/api-dto" + }, + { + "path": "../packages/backend-common" + }, + { + "path": "../packages/shared" + }, + { + "path": "../packages/validation" + } + ] } diff --git a/backend/tsconfig.test.json b/backend/tsconfig.test.json new file mode 100644 index 000000000..58488a4ff --- /dev/null +++ b/backend/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowJs": true, + "noImplicitAny": false + }, + "include": ["src/**/*", "tests/**/*", "../packages/**/*"] +} diff --git a/backend/webpack.config.js b/backend/webpack.config.js new file mode 100644 index 000000000..11d5bc5fc --- /dev/null +++ b/backend/webpack.config.js @@ -0,0 +1,110 @@ +const nodeExternals = require('webpack-node-externals'); +const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); +const path = require('path'); +const webpack = require('webpack'); + +module.exports = function (options, webpackOptions) { + const isProd = + webpackOptions.mode === 'production' || + process.env.NODE_ENV === 'production'; + + return { + ...options, + entry: isProd + ? path.resolve(__dirname, 'src/main.ts') + : ['webpack/hot/poll?100', path.resolve(__dirname, 'src/main.ts')], + externals: [ + !isProd && + nodeExternals({ + allowlist: [ + 'webpack/hot/poll?100', + /^@kleinkram/, + /^@backend-common/, + ], + }), + ].filter(Boolean), + module: { + ...options.module, + rules: [ + { + test: /\.node$/, + loader: 'node-loader', + }, + { + test: /\.ts$/, + exclude: /node_modules/, + use: { + loader: 'ts-loader', + options: { + transpileOnly: true, + configFile: path.resolve( + __dirname, + 'tsconfig.json', + ), + logLevel: 'info', + }, + }, + }, + ], + }, + plugins: [ + ...options.plugins, + !isProd && new webpack.HotModuleReplacementPlugin(), + !isProd && + new webpack.WatchIgnorePlugin({ + paths: [/\.js$/, /\.d\.ts$/], + }), + !isProd && + options.watch && + new RunScriptWebpackPlugin({ + name: options.output.filename, + autoRestart: true, + }), + ].filter(Boolean), + resolve: { + ...options.resolve, + alias: { + ...options.resolve?.alias, + '@backend-common': path.resolve( + __dirname, + '../packages/backend-common/src', + ), + '@kleinkram/backend-common': path.resolve( + __dirname, + '../packages/backend-common/src', + ), + '@kleinkram/shared': path.resolve( + __dirname, + '../packages/shared/src/index.ts', + ), + '@kleinkram/validation': path.resolve( + __dirname, + '../packages/validation/src/index.ts', + ), + '@kleinkram/api-dto': path.resolve( + __dirname, + '../packages/api-dto/src/index.ts', + ), + typeorm: path.resolve(__dirname, 'node_modules/typeorm'), + '@nestjs/typeorm': path.resolve( + __dirname, + 'node_modules/@nestjs/typeorm', + ), + '@nestjs/common': path.resolve( + __dirname, + 'node_modules/@nestjs/common', + ), + '@nestjs/core': path.resolve( + __dirname, + 'node_modules/@nestjs/core', + ), + }, + }, + watchOptions: { + ignored: /node_modules/, + }, + optimization: { + minimize: false, + }, + }; +}; diff --git a/backend/yarn.lock b/backend/yarn.lock deleted file mode 100644 index 39023cae8..000000000 --- a/backend/yarn.lock +++ /dev/null @@ -1,9197 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@angular-devkit/core@19.2.15": - version "19.2.15" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-19.2.15.tgz#35af566f9c69d3eca9c183936ee8527d9725a006" - integrity sha512-pU2RZYX6vhd7uLSdLwPnuBcr0mXJSjp3EgOXKsrlQFQZevc+Qs+2JdXgIElnOT/aDqtRtriDmLlSbtdE8n3ZbA== - dependencies: - ajv "8.17.1" - ajv-formats "3.0.1" - jsonc-parser "3.3.1" - picomatch "4.0.2" - rxjs "7.8.1" - source-map "0.7.4" - -"@angular-devkit/core@19.2.17": - version "19.2.17" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-19.2.17.tgz#014107a94240dd3ecf38edfcf23113ad55b9752b" - integrity sha512-Ah008x2RJkd0F+NLKqIpA34/vUGwjlprRCkvddjDopAWRzYn6xCkz1Tqwuhn0nR1Dy47wTLKYD999TYl5ONOAQ== - dependencies: - ajv "8.17.1" - ajv-formats "3.0.1" - jsonc-parser "3.3.1" - picomatch "4.0.2" - rxjs "7.8.1" - source-map "0.7.4" - -"@angular-devkit/schematics-cli@19.2.15": - version "19.2.15" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-19.2.15.tgz#e68a5a1c968ee975168812df8067129d90d11a32" - integrity sha512-1ESFmFGMpGQmalDB3t2EtmWDGv6gOFYBMxmHO2f1KI/UDl8UmZnCGL4mD3EWo8Hv0YIsZ9wOH9Q7ZHNYjeSpzg== - dependencies: - "@angular-devkit/core" "19.2.15" - "@angular-devkit/schematics" "19.2.15" - "@inquirer/prompts" "7.3.2" - ansi-colors "4.1.3" - symbol-observable "4.0.0" - yargs-parser "21.1.1" - -"@angular-devkit/schematics@19.2.15": - version "19.2.15" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-19.2.15.tgz#d20ceba32f97b5f0e07e25268d9b8fea7ee142dd" - integrity sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg== - dependencies: - "@angular-devkit/core" "19.2.15" - jsonc-parser "3.3.1" - magic-string "0.30.17" - ora "5.4.1" - rxjs "7.8.1" - -"@angular-devkit/schematics@19.2.17": - version "19.2.17" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-19.2.17.tgz#253c0c6f4d5400c3bf038d73ed114af5516b72ef" - integrity sha512-ADfbaBsrG8mBF6Mfs+crKA/2ykB8AJI50Cv9tKmZfwcUcyAdmTr+vVvhsBCfvUAEokigSsgqgpYxfkJVxhJYeg== - dependencies: - "@angular-devkit/core" "19.2.17" - jsonc-parser "3.3.1" - magic-string "0.30.17" - ora "5.4.1" - rxjs "7.8.1" - -"@aws-crypto/crc32@5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz#cfcc22570949c98c6689cfcbd2d693d36cdae2e1" - integrity sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg== - dependencies: - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - tslib "^2.6.2" - -"@aws-crypto/crc32c@5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz#4e34aab7f419307821509a98b9b08e84e0c1917e" - integrity sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag== - dependencies: - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - tslib "^2.6.2" - -"@aws-crypto/sha1-browser@5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz#b0ee2d2821d3861f017e965ef3b4cb38e3b6a0f4" - integrity sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg== - dependencies: - "@aws-crypto/supports-web-crypto" "^5.2.0" - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.6.2" - -"@aws-crypto/sha256-browser@5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz#153895ef1dba6f9fce38af550e0ef58988eb649e" - integrity sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== - dependencies: - "@aws-crypto/sha256-js" "^5.2.0" - "@aws-crypto/supports-web-crypto" "^5.2.0" - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.6.2" - -"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz#c4fdb773fdbed9a664fc1a95724e206cf3860042" - integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== - dependencies: - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - tslib "^2.6.2" - -"@aws-crypto/supports-web-crypto@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz#a1e399af29269be08e695109aa15da0a07b5b5fb" - integrity sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== - dependencies: - tslib "^2.6.2" - -"@aws-crypto/util@5.2.0", "@aws-crypto/util@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-5.2.0.tgz#71284c9cffe7927ddadac793c14f14886d3876da" - integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== - dependencies: - "@aws-sdk/types" "^3.222.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.6.2" - -"@aws-sdk/client-s3@3.726.1": - version "3.726.1" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.726.1.tgz#05e9ae74be18758fc9d05a053777a8bb919fb24c" - integrity sha512-UpOGcob87DiuS2d3fW6vDZg94g57mNiOSkzvR/6GOdvBSlUgk8LLwVzGASB71FdKMl1EGEr4MeD5uKH9JsG+dw== - dependencies: - "@aws-crypto/sha1-browser" "5.2.0" - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/client-sso-oidc" "3.726.0" - "@aws-sdk/client-sts" "3.726.1" - "@aws-sdk/core" "3.723.0" - "@aws-sdk/credential-provider-node" "3.726.0" - "@aws-sdk/middleware-bucket-endpoint" "3.726.0" - "@aws-sdk/middleware-expect-continue" "3.723.0" - "@aws-sdk/middleware-flexible-checksums" "3.723.0" - "@aws-sdk/middleware-host-header" "3.723.0" - "@aws-sdk/middleware-location-constraint" "3.723.0" - "@aws-sdk/middleware-logger" "3.723.0" - "@aws-sdk/middleware-recursion-detection" "3.723.0" - "@aws-sdk/middleware-sdk-s3" "3.723.0" - "@aws-sdk/middleware-ssec" "3.723.0" - "@aws-sdk/middleware-user-agent" "3.726.0" - "@aws-sdk/region-config-resolver" "3.723.0" - "@aws-sdk/signature-v4-multi-region" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@aws-sdk/util-endpoints" "3.726.0" - "@aws-sdk/util-user-agent-browser" "3.723.0" - "@aws-sdk/util-user-agent-node" "3.726.0" - "@aws-sdk/xml-builder" "3.723.0" - "@smithy/config-resolver" "^4.0.0" - "@smithy/core" "^3.0.0" - "@smithy/eventstream-serde-browser" "^4.0.0" - "@smithy/eventstream-serde-config-resolver" "^4.0.0" - "@smithy/eventstream-serde-node" "^4.0.0" - "@smithy/fetch-http-handler" "^5.0.0" - "@smithy/hash-blob-browser" "^4.0.0" - "@smithy/hash-node" "^4.0.0" - "@smithy/hash-stream-node" "^4.0.0" - "@smithy/invalid-dependency" "^4.0.0" - "@smithy/md5-js" "^4.0.0" - "@smithy/middleware-content-length" "^4.0.0" - "@smithy/middleware-endpoint" "^4.0.0" - "@smithy/middleware-retry" "^4.0.0" - "@smithy/middleware-serde" "^4.0.0" - "@smithy/middleware-stack" "^4.0.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/node-http-handler" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/smithy-client" "^4.0.0" - "@smithy/types" "^4.0.0" - "@smithy/url-parser" "^4.0.0" - "@smithy/util-base64" "^4.0.0" - "@smithy/util-body-length-browser" "^4.0.0" - "@smithy/util-body-length-node" "^4.0.0" - "@smithy/util-defaults-mode-browser" "^4.0.0" - "@smithy/util-defaults-mode-node" "^4.0.0" - "@smithy/util-endpoints" "^3.0.0" - "@smithy/util-middleware" "^4.0.0" - "@smithy/util-retry" "^4.0.0" - "@smithy/util-stream" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - "@smithy/util-waiter" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/client-sso-oidc@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.726.0.tgz#6c83f6f95f15a7557f84c0d9ccd3f489368601a8" - integrity sha512-5JzTX9jwev7+y2Jkzjz0pd1wobB5JQfPOQF3N2DrJ5Pao0/k6uRYwE4NqB0p0HlGrMTDm7xNq7OSPPIPG575Jw== - dependencies: - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "3.723.0" - "@aws-sdk/credential-provider-node" "3.726.0" - "@aws-sdk/middleware-host-header" "3.723.0" - "@aws-sdk/middleware-logger" "3.723.0" - "@aws-sdk/middleware-recursion-detection" "3.723.0" - "@aws-sdk/middleware-user-agent" "3.726.0" - "@aws-sdk/region-config-resolver" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@aws-sdk/util-endpoints" "3.726.0" - "@aws-sdk/util-user-agent-browser" "3.723.0" - "@aws-sdk/util-user-agent-node" "3.726.0" - "@smithy/config-resolver" "^4.0.0" - "@smithy/core" "^3.0.0" - "@smithy/fetch-http-handler" "^5.0.0" - "@smithy/hash-node" "^4.0.0" - "@smithy/invalid-dependency" "^4.0.0" - "@smithy/middleware-content-length" "^4.0.0" - "@smithy/middleware-endpoint" "^4.0.0" - "@smithy/middleware-retry" "^4.0.0" - "@smithy/middleware-serde" "^4.0.0" - "@smithy/middleware-stack" "^4.0.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/node-http-handler" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/smithy-client" "^4.0.0" - "@smithy/types" "^4.0.0" - "@smithy/url-parser" "^4.0.0" - "@smithy/util-base64" "^4.0.0" - "@smithy/util-body-length-browser" "^4.0.0" - "@smithy/util-body-length-node" "^4.0.0" - "@smithy/util-defaults-mode-browser" "^4.0.0" - "@smithy/util-defaults-mode-node" "^4.0.0" - "@smithy/util-endpoints" "^3.0.0" - "@smithy/util-middleware" "^4.0.0" - "@smithy/util-retry" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/client-sso@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.726.0.tgz#802a9a8d22db029361b859ae4a079ad680c98db4" - integrity sha512-NM5pjv2qglEc4XN3nnDqtqGsSGv1k5YTmzDo3W3pObItHmpS8grSeNfX9zSH+aVl0Q8hE4ZIgvTPNZ+GzwVlqg== - dependencies: - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "3.723.0" - "@aws-sdk/middleware-host-header" "3.723.0" - "@aws-sdk/middleware-logger" "3.723.0" - "@aws-sdk/middleware-recursion-detection" "3.723.0" - "@aws-sdk/middleware-user-agent" "3.726.0" - "@aws-sdk/region-config-resolver" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@aws-sdk/util-endpoints" "3.726.0" - "@aws-sdk/util-user-agent-browser" "3.723.0" - "@aws-sdk/util-user-agent-node" "3.726.0" - "@smithy/config-resolver" "^4.0.0" - "@smithy/core" "^3.0.0" - "@smithy/fetch-http-handler" "^5.0.0" - "@smithy/hash-node" "^4.0.0" - "@smithy/invalid-dependency" "^4.0.0" - "@smithy/middleware-content-length" "^4.0.0" - "@smithy/middleware-endpoint" "^4.0.0" - "@smithy/middleware-retry" "^4.0.0" - "@smithy/middleware-serde" "^4.0.0" - "@smithy/middleware-stack" "^4.0.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/node-http-handler" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/smithy-client" "^4.0.0" - "@smithy/types" "^4.0.0" - "@smithy/url-parser" "^4.0.0" - "@smithy/util-base64" "^4.0.0" - "@smithy/util-body-length-browser" "^4.0.0" - "@smithy/util-body-length-node" "^4.0.0" - "@smithy/util-defaults-mode-browser" "^4.0.0" - "@smithy/util-defaults-mode-node" "^4.0.0" - "@smithy/util-endpoints" "^3.0.0" - "@smithy/util-middleware" "^4.0.0" - "@smithy/util-retry" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/client-sts@3.726.1": - version "3.726.1" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.726.1.tgz#49ab471db7e04792db24e181f8bb8c7787739b34" - integrity sha512-qh9Q9Vu1hrM/wMBOBIaskwnE4GTFaZu26Q6WHwyWNfj7J8a40vBxpW16c2vYXHLBtwRKM1be8uRLkmDwghpiNw== - dependencies: - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/client-sso-oidc" "3.726.0" - "@aws-sdk/core" "3.723.0" - "@aws-sdk/credential-provider-node" "3.726.0" - "@aws-sdk/middleware-host-header" "3.723.0" - "@aws-sdk/middleware-logger" "3.723.0" - "@aws-sdk/middleware-recursion-detection" "3.723.0" - "@aws-sdk/middleware-user-agent" "3.726.0" - "@aws-sdk/region-config-resolver" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@aws-sdk/util-endpoints" "3.726.0" - "@aws-sdk/util-user-agent-browser" "3.723.0" - "@aws-sdk/util-user-agent-node" "3.726.0" - "@smithy/config-resolver" "^4.0.0" - "@smithy/core" "^3.0.0" - "@smithy/fetch-http-handler" "^5.0.0" - "@smithy/hash-node" "^4.0.0" - "@smithy/invalid-dependency" "^4.0.0" - "@smithy/middleware-content-length" "^4.0.0" - "@smithy/middleware-endpoint" "^4.0.0" - "@smithy/middleware-retry" "^4.0.0" - "@smithy/middleware-serde" "^4.0.0" - "@smithy/middleware-stack" "^4.0.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/node-http-handler" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/smithy-client" "^4.0.0" - "@smithy/types" "^4.0.0" - "@smithy/url-parser" "^4.0.0" - "@smithy/util-base64" "^4.0.0" - "@smithy/util-body-length-browser" "^4.0.0" - "@smithy/util-body-length-node" "^4.0.0" - "@smithy/util-defaults-mode-browser" "^4.0.0" - "@smithy/util-defaults-mode-node" "^4.0.0" - "@smithy/util-endpoints" "^3.0.0" - "@smithy/util-middleware" "^4.0.0" - "@smithy/util-retry" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/core@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.723.0.tgz#7a441b1362fa22609f80ede42d4e069829b9b4d1" - integrity sha512-UraXNmvqj3vScSsTkjMwQkhei30BhXlW5WxX6JacMKVtl95c7z0qOXquTWeTalYkFfulfdirUhvSZrl+hcyqTw== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/core" "^3.0.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/signature-v4" "^5.0.0" - "@smithy/smithy-client" "^4.0.0" - "@smithy/types" "^4.0.0" - "@smithy/util-middleware" "^4.0.0" - fast-xml-parser "4.4.1" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-env@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.723.0.tgz#7d85014d21ce50f9f6a108c5c673e87c54860eaa" - integrity sha512-OuH2yULYUHTVDUotBoP/9AEUIJPn81GQ/YBtZLoo2QyezRJ2QiO/1epVtbJlhNZRwXrToLEDmQGA2QfC8c7pbA== - dependencies: - "@aws-sdk/core" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-http@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.723.0.tgz#3b5db3225bb6dd97fecf22e18c06c3567eb1bce4" - integrity sha512-DTsKC6xo/kz/ZSs1IcdbQMTgiYbpGTGEd83kngFc1bzmw7AmK92DBZKNZpumf8R/UfSpTcj9zzUUmrWz1kD0eQ== - dependencies: - "@aws-sdk/core" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/fetch-http-handler" "^5.0.0" - "@smithy/node-http-handler" "^4.0.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/smithy-client" "^4.0.0" - "@smithy/types" "^4.0.0" - "@smithy/util-stream" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-ini@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.726.0.tgz#25115ecb3814f3f8e106cf12f5f34ab247095244" - integrity sha512-seTtcKL2+gZX6yK1QRPr5mDJIBOatrpoyrO8D5b8plYtV/PDbDW3mtDJSWFHet29G61ZmlNElyXRqQCXn9WX+A== - dependencies: - "@aws-sdk/core" "3.723.0" - "@aws-sdk/credential-provider-env" "3.723.0" - "@aws-sdk/credential-provider-http" "3.723.0" - "@aws-sdk/credential-provider-process" "3.723.0" - "@aws-sdk/credential-provider-sso" "3.726.0" - "@aws-sdk/credential-provider-web-identity" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/credential-provider-imds" "^4.0.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/shared-ini-file-loader" "^4.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-node@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.726.0.tgz#a997ea8e8e871e77cbebf6c8a6179d6f6af8897c" - integrity sha512-jjsewBcw/uLi24x8JbnuDjJad4VA9ROCE94uVRbEnGmUEsds75FWOKp3fWZLQlmjLtzsIbJOZLALkZP86liPaw== - dependencies: - "@aws-sdk/credential-provider-env" "3.723.0" - "@aws-sdk/credential-provider-http" "3.723.0" - "@aws-sdk/credential-provider-ini" "3.726.0" - "@aws-sdk/credential-provider-process" "3.723.0" - "@aws-sdk/credential-provider-sso" "3.726.0" - "@aws-sdk/credential-provider-web-identity" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/credential-provider-imds" "^4.0.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/shared-ini-file-loader" "^4.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-process@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.723.0.tgz#32bc55573b0a8f31e69b15939202d266adbbe711" - integrity sha512-fgupvUjz1+jeoCBA7GMv0L6xEk92IN6VdF4YcFhsgRHlHvNgm7ayaoKQg7pz2JAAhG/3jPX6fp0ASNy+xOhmPA== - dependencies: - "@aws-sdk/core" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/shared-ini-file-loader" "^4.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-sso@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.726.0.tgz#460dbc65e3d8dfd151d7b41e2da85ba7e7cc1f0a" - integrity sha512-WxkN76WeB08j2yw7jUH9yCMPxmT9eBFd9ZA/aACG7yzOIlsz7gvG3P2FQ0tVg25GHM0E4PdU3p/ByTOawzcOAg== - dependencies: - "@aws-sdk/client-sso" "3.726.0" - "@aws-sdk/core" "3.723.0" - "@aws-sdk/token-providers" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/shared-ini-file-loader" "^4.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-web-identity@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.723.0.tgz#5c17ea243b05b4dca0584db597ac68d8509dd754" - integrity sha512-tl7pojbFbr3qLcOE6xWaNCf1zEfZrIdSJtOPeSXfV/thFMMAvIjgf3YN6Zo1a6cxGee8zrV/C8PgOH33n+Ev/A== - dependencies: - "@aws-sdk/core" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-bucket-endpoint@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.726.0.tgz#9ba221dcc75f0415b2c854400477454aa87992d2" - integrity sha512-vpaP80rZqwu0C3ELayIcRIW84/nd1tadeoqllT+N9TDshuEvq4UJ+w47OBHB7RkHFJoc79lXXNYle0fdQdaE/A== - dependencies: - "@aws-sdk/types" "3.723.0" - "@aws-sdk/util-arn-parser" "3.723.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/types" "^4.0.0" - "@smithy/util-config-provider" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-expect-continue@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.723.0.tgz#59addac9b4cdc958ea1e06de9863db657e9c8e43" - integrity sha512-w/O0EkIzkiqvGu7U8Ke7tue0V0HYM5dZQrz6nVU+R8T2LddWJ+njEIHU4Wh8aHPLQXdZA5NQumv0xLPdEutykw== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-flexible-checksums@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.723.0.tgz#612ec13c4ba5dc906793172ece02a95059fffcc6" - integrity sha512-JY76mrUCLa0FHeMZp8X9+KK6uEuZaRZaQrlgq6zkXX/3udukH0T3YdFC+Y9uw5ddbiwZ5+KwgmlhnPpiXKfP4g== - dependencies: - "@aws-crypto/crc32" "5.2.0" - "@aws-crypto/crc32c" "5.2.0" - "@aws-crypto/util" "5.2.0" - "@aws-sdk/core" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/is-array-buffer" "^4.0.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/types" "^4.0.0" - "@smithy/util-middleware" "^4.0.0" - "@smithy/util-stream" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-host-header@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.723.0.tgz#f043689755e5b45ee6500b0d0a7090d9b4a864f7" - integrity sha512-LLVzLvk299pd7v4jN9yOSaWDZDfH0SnBPb6q+FDPaOCMGBY8kuwQso7e/ozIKSmZHRMGO3IZrflasHM+rI+2YQ== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-location-constraint@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.723.0.tgz#364e875a511d97431b6d337878c8a9bd5e2fdf64" - integrity sha512-inp9tyrdRWjGOMu1rzli8i2gTo0P4X6L7nNRXNTKfyPNZcBimZ4H0H1B671JofSI5isaklVy5r4pvv2VjjLSHw== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-logger@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.723.0.tgz#e8718056fc2d73a0d51308cad20676228be26652" - integrity sha512-chASQfDG5NJ8s5smydOEnNK7N0gDMyuPbx7dYYcm1t/PKtnVfvWF+DHCTrRC2Ej76gLJVCVizlAJKM8v8Kg3cg== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-recursion-detection@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.723.0.tgz#b4557c7f554492f56eeb0cbf5bc02dac7ef102a8" - integrity sha512-7usZMtoynT9/jxL/rkuDOFQ0C2mhXl4yCm67Rg7GNTstl67u7w5WN1aIRImMeztaKlw8ExjoTyo6WTs1Kceh7A== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-sdk-s3@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.723.0.tgz#d323c24b2268933bf51353d5215fa8baadaf8837" - integrity sha512-wfjOvNJVp8LDWhq4wO5jtSMb8Vgf4tNlR7QTEQfoYc6AGU3WlK5xyUQcpfcpwytEhQTN9u0cJLQpSyXDO+qSCw== - dependencies: - "@aws-sdk/core" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@aws-sdk/util-arn-parser" "3.723.0" - "@smithy/core" "^3.0.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/signature-v4" "^5.0.0" - "@smithy/smithy-client" "^4.0.0" - "@smithy/types" "^4.0.0" - "@smithy/util-config-provider" "^4.0.0" - "@smithy/util-middleware" "^4.0.0" - "@smithy/util-stream" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-ssec@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.723.0.tgz#b4adb65eb4ac029ee8b566f373b1d54aecbbd7ad" - integrity sha512-Bs+8RAeSMik6ZYCGSDJzJieGsDDh2fRbh1HQG94T8kpwBXVxMYihm6e9Xp2cyl+w9fyyCnh0IdCKChP/DvrdhA== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-user-agent@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.726.0.tgz#d8a791c61adca1f26332ce5128da7aa6c1433e89" - integrity sha512-hZvzuE5S0JmFie1r68K2wQvJbzyxJFdzltj9skgnnwdvLe8F/tz7MqLkm28uV0m4jeHk0LpiBo6eZaPkQiwsZQ== - dependencies: - "@aws-sdk/core" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@aws-sdk/util-endpoints" "3.726.0" - "@smithy/core" "^3.0.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/region-config-resolver@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.723.0.tgz#07b7ee4788ec7a7f5638bbbe0f9f7565125caf22" - integrity sha512-tGF/Cvch3uQjZIj34LY2mg8M2Dr4kYG8VU8Yd0dFnB1ybOEOveIK/9ypUo9ycZpB9oO6q01KRe5ijBaxNueUQg== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/types" "^4.0.0" - "@smithy/util-config-provider" "^4.0.0" - "@smithy/util-middleware" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/signature-v4-multi-region@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.723.0.tgz#1de81c7ee98410dabbb22978bc5d4540c51a8afa" - integrity sha512-lJlVAa5Sl589qO8lwMLVUtnlF1Q7I+6k1Iomv2goY9d1bRl4q2N5Pit2qJVr2AMW0sceQXeh23i2a/CKOqVAdg== - dependencies: - "@aws-sdk/middleware-sdk-s3" "3.723.0" - "@aws-sdk/types" "3.723.0" - "@smithy/protocol-http" "^5.0.0" - "@smithy/signature-v4" "^5.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/token-providers@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.723.0.tgz#ae173a18783886e592212abb820d28cbdb9d9237" - integrity sha512-hniWi1x4JHVwKElANh9afKIMUhAutHVBRD8zo6usr0PAoj+Waf220+1ULS74GXtLXAPCiNXl5Og+PHA7xT8ElQ== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/property-provider" "^4.0.0" - "@smithy/shared-ini-file-loader" "^4.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/types@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.723.0.tgz#f0c5a6024a73470421c469b6c1dd5bc4b8fb851b" - integrity sha512-LmK3kwiMZG1y5g3LGihT9mNkeNOmwEyPk6HGcJqh0wOSV4QpWoKu2epyKE4MLQNUUlz2kOVbVbOrwmI6ZcteuA== - dependencies: - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/types@^3.222.0": - version "3.775.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.775.0.tgz#09863a9e68c080947db7c3d226d1c56b8f0f5150" - integrity sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@aws-sdk/util-arn-parser@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.723.0.tgz#e9bff2b13918a92d60e0012101dad60ed7db292c" - integrity sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w== - dependencies: - tslib "^2.6.2" - -"@aws-sdk/util-endpoints@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.726.0.tgz#0b39d4e2fe4b8b4a35d7e3714f1ed126114befd9" - integrity sha512-sLd30ASsPMoPn3XBK50oe/bkpJ4N8Bpb7SbhoxcY3Lk+fSASaWxbbXE81nbvCnkxrZCvkPOiDHzJCp1E2im71A== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/types" "^4.0.0" - "@smithy/util-endpoints" "^3.0.0" - tslib "^2.6.2" - -"@aws-sdk/util-locate-window@^3.0.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz#174551bfdd2eb36d3c16e7023fd7e7ee96ad0fa9" - integrity sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw== - dependencies: - tslib "^2.6.2" - -"@aws-sdk/util-user-agent-browser@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.723.0.tgz#64b0b4413c1be1585f95c3e2606429cc9f86df83" - integrity sha512-Wh9I6j2jLhNFq6fmXydIpqD1WyQLyTfSxjW9B+PXSnPyk3jtQW8AKQur7p97rO8LAUzVI0bv8kb3ZzDEVbquIg== - dependencies: - "@aws-sdk/types" "3.723.0" - "@smithy/types" "^4.0.0" - bowser "^2.11.0" - tslib "^2.6.2" - -"@aws-sdk/util-user-agent-node@3.726.0": - version "3.726.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.726.0.tgz#f093568a730b0d58ef7eca231f27309b11b8ef61" - integrity sha512-iEj6KX9o6IQf23oziorveRqyzyclWai95oZHDJtYav3fvLJKStwSjygO4xSF7ycHcTYeCHSLO1FFOHgGVs4Viw== - dependencies: - "@aws-sdk/middleware-user-agent" "3.726.0" - "@aws-sdk/types" "3.723.0" - "@smithy/node-config-provider" "^4.0.0" - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@aws-sdk/xml-builder@3.723.0": - version "3.723.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.723.0.tgz#989580d65086985b82f05eaea0ee46d78a510398" - integrity sha512-5xK2SqGU1mzzsOeemy7cy3fGKxR1sEpUs4pEiIjaT0OIvU+fZaDVUEYWOqsgns6wI90XZEQJlXtI8uAHX/do5Q== - dependencies: - "@smithy/types" "^4.0.0" - tslib "^2.6.2" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== - dependencies: - "@babel/helper-validator-identifier" "^7.25.9" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.26.8": - version "7.26.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" - integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== - -"@babel/compat-data@^7.27.2": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" - integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" - integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helpers" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/traverse" "^7.26.10" - "@babel/types" "^7.26.10" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/core@^7.27.4": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.0.tgz#55dad808d5bf3445a108eefc88ea3fdf034749a4" - integrity sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.0" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-module-transforms" "^7.27.3" - "@babel/helpers" "^7.27.6" - "@babel/parser" "^7.28.0" - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.0" - "@babel/types" "^7.28.0" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.26.10", "@babel/generator@^7.27.0", "@babel/generator@^7.7.2": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.0.tgz#764382b5392e5b9aff93cadb190d0745866cbc2c" - integrity sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw== - dependencies: - "@babel/parser" "^7.27.0" - "@babel/types" "^7.27.0" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/generator@^7.27.5", "@babel/generator@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" - integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== - dependencies: - "@babel/parser" "^7.28.0" - "@babel/types" "^7.28.0" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.26.5": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz#de0c753b1cd1d9ab55d473c5a5cf7170f0a81880" - integrity sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA== - dependencies: - "@babel/compat-data" "^7.26.8" - "@babel/helper-validator-option" "^7.25.9" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-compilation-targets@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== - dependencies: - "@babel/compat-data" "^7.27.2" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-globals@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" - integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== - -"@babel/helper-module-imports@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" - integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-module-transforms@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02" - integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.27.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" - integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== - -"@babel/helper-plugin-utils@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" - integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-validator-identifier@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== - -"@babel/helper-validator-option@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" - integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== - -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helpers@^7.26.10": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.0.tgz#53d156098defa8243eab0f32fa17589075a1b808" - integrity sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg== - dependencies: - "@babel/template" "^7.27.0" - "@babel/types" "^7.27.0" - -"@babel/helpers@^7.27.6": - version "7.28.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.2.tgz#80f0918fecbfebea9af856c419763230040ee850" - integrity sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw== - dependencies: - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.2" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.26.10", "@babel/parser@^7.27.0": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.0.tgz#3d7d6ee268e41d2600091cbd4e145ffee85a44ec" - integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg== - dependencies: - "@babel/types" "^7.27.0" - -"@babel/parser@^7.27.2", "@babel/parser@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" - integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== - dependencies: - "@babel/types" "^7.28.0" - -"@babel/parser@^7.28.3": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" - integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== - dependencies: - "@babel/types" "^7.28.4" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" - integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" - integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" - integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" - integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/template@^7.26.9", "@babel/template@^7.27.0", "@babel/template@^7.3.3": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4" - integrity sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/parser" "^7.27.0" - "@babel/types" "^7.27.0" - -"@babel/template@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.0.tgz#11d7e644779e166c0442f9a07274d02cd91d4a70" - integrity sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.27.0" - "@babel/parser" "^7.27.0" - "@babel/template" "^7.27.0" - "@babel/types" "^7.27.0" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" - integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.0" - "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.0" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.0" - debug "^4.3.1" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.3.3": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.0.tgz#ef9acb6b06c3173f6632d993ecb6d4ae470b4559" - integrity sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - -"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.0", "@babel/types@^7.28.2": - version "7.28.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" - integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - -"@babel/types@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" - integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@colors/colors@1.6.0", "@colors/colors@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" - integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@dabh/diagnostics@^2.0.8": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.8.tgz#ead97e72ca312cf0e6dd7af0d300b58993a31a5e" - integrity sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q== - dependencies: - "@so-ric/colorspace" "^1.1.6" - enabled "2.0.x" - kuler "^2.0.0" - -"@eslint-community/eslint-utils@^4.7.0", "@eslint-community/eslint-utils@^4.8.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" - integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint/config-array@^0.21.0": - version "0.21.0" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" - integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== - dependencies: - "@eslint/object-schema" "^2.1.6" - debug "^4.3.1" - minimatch "^3.1.2" - -"@eslint/config-helpers@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.0.tgz#e9f94ba3b5b875e32205cb83fece18e64486e9e6" - integrity sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog== - dependencies: - "@eslint/core" "^0.16.0" - -"@eslint/core@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.16.0.tgz#490254f275ba9667ddbab344f4f0a6b7a7bd7209" - integrity sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/eslintrc@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" - integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@9.37.0": - version "9.37.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.37.0.tgz#0cfd5aa763fe5d1ee60bedf84cd14f54bcf9e21b" - integrity sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg== - -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== - -"@eslint/plugin-kit@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz#f6a245b42886abf6fc9c7ab7744a932250335ab2" - integrity sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A== - dependencies: - "@eslint/core" "^0.16.0" - levn "^0.4.1" - -"@google-cloud/local-auth@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@google-cloud/local-auth/-/local-auth-3.0.1.tgz#e4e593eea4de344ea997ef36e8ca90aac91fc69b" - integrity sha512-YJ3GFbksfHyEarbVHPSCzhKpjbnlAhdzg2SEf79l6ODukrSM1qUOqfopY232Xkw26huKSndyzmJz+A6b2WYn7Q== - dependencies: - arrify "^2.0.1" - google-auth-library "^9.0.0" - open "^7.0.3" - server-destroy "^1.0.1" - -"@grpc/grpc-js@^1.7.1": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.13.3.tgz#6ad08d186c2a8651697085f790c5c68eaca45904" - integrity sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg== - dependencies: - "@grpc/proto-loader" "^0.7.13" - "@js-sdsl/ordered-map" "^4.4.2" - -"@grpc/proto-loader@^0.7.13": - version "0.7.15" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60" - integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.2.5" - yargs "^17.7.2" - -"@humanfs/core@^0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" - integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== - -"@humanfs/node@^0.16.6": - version "0.16.6" - resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" - integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== - dependencies: - "@humanfs/core" "^0.19.1" - "@humanwhocodes/retry" "^0.3.0" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/retry@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" - integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== - -"@humanwhocodes/retry@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" - integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== - -"@inquirer/checkbox@^4.1.2": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-4.1.5.tgz#891bb32ca98eb6ee2889f71d79722705e2241161" - integrity sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/figures" "^1.0.11" - "@inquirer/type" "^3.0.6" - ansi-escapes "^4.3.2" - yoctocolors-cjs "^2.1.2" - -"@inquirer/checkbox@^4.2.0": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-4.2.2.tgz#eabaa7eb6adbd64bb7bb7765c67c0a283ed616eb" - integrity sha512-E+KExNurKcUJJdxmjglTl141EwxWyAHplvsYJQgSwXf8qiNWkTxTuCCqmhFEmbIXd4zLaGMfQFJ6WrZ7fSeV3g== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/figures" "^1.0.13" - "@inquirer/type" "^3.0.8" - ansi-escapes "^4.3.2" - yoctocolors-cjs "^2.1.2" - -"@inquirer/confirm@^5.1.14": - version "5.1.16" - resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.16.tgz#4f99603e5c8a1b471b819343f708c75e8abd2b88" - integrity sha512-j1a5VstaK5KQy8Mu8cHmuQvN1Zc62TbLhjJxwHvKPPKEoowSF6h/0UdOpA9DNdWZ+9Inq73+puRq1df6OJ8Sag== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/type" "^3.0.8" - -"@inquirer/confirm@^5.1.6": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.9.tgz#c858b6a3decb458241ec36ca9a9117477338076a" - integrity sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/type" "^3.0.6" - -"@inquirer/core@^10.1.10": - version "10.1.10" - resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.10.tgz#222a374e3768536a1eb0adf7516c436d5f4a291d" - integrity sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw== - dependencies: - "@inquirer/figures" "^1.0.11" - "@inquirer/type" "^3.0.6" - ansi-escapes "^4.3.2" - cli-width "^4.1.0" - mute-stream "^2.0.0" - signal-exit "^4.1.0" - wrap-ansi "^6.2.0" - yoctocolors-cjs "^2.1.2" - -"@inquirer/core@^10.2.0": - version "10.2.0" - resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.2.0.tgz#19ff527dbe0956891d825e320ecbc890bd6a1550" - integrity sha512-NyDSjPqhSvpZEMZrLCYUquWNl+XC/moEcVFqS55IEYIYsY0a1cUCevSqk7ctOlnm/RaSBU5psFryNlxcmGrjaA== - dependencies: - "@inquirer/figures" "^1.0.13" - "@inquirer/type" "^3.0.8" - ansi-escapes "^4.3.2" - cli-width "^4.1.0" - mute-stream "^2.0.0" - signal-exit "^4.1.0" - wrap-ansi "^6.2.0" - yoctocolors-cjs "^2.1.2" - -"@inquirer/editor@^4.2.15": - version "4.2.18" - resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-4.2.18.tgz#1418aef90025046ad16306451effb6fb36db9664" - integrity sha512-yeQN3AXjCm7+Hmq5L6Dm2wEDeBRdAZuyZ4I7tWSSanbxDzqM0KqzoDbKM7p4ebllAYdoQuPJS6N71/3L281i6w== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/external-editor" "^1.0.1" - "@inquirer/type" "^3.0.8" - -"@inquirer/editor@^4.2.7": - version "4.2.10" - resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-4.2.10.tgz#45e399313ee857857248bd539b8e832aa0fb60b3" - integrity sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/type" "^3.0.6" - external-editor "^3.1.0" - -"@inquirer/expand@^4.0.17": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-4.0.18.tgz#8bf1bcd1ee99b8fa02e1143ed5bf69dc576bacd7" - integrity sha512-xUjteYtavH7HwDMzq4Cn2X4Qsh5NozoDHCJTdoXg9HfZ4w3R6mxV1B9tL7DGJX2eq/zqtsFjhm0/RJIMGlh3ag== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/type" "^3.0.8" - yoctocolors-cjs "^2.1.2" - -"@inquirer/expand@^4.0.9": - version "4.0.12" - resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-4.0.12.tgz#1e4554f509a435f966e2b91395a503d77df35c17" - integrity sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/type" "^3.0.6" - yoctocolors-cjs "^2.1.2" - -"@inquirer/external-editor@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@inquirer/external-editor/-/external-editor-1.0.1.tgz#ab0a82c5719a963fb469021cde5cd2b74fea30f8" - integrity sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q== - dependencies: - chardet "^2.1.0" - iconv-lite "^0.6.3" - -"@inquirer/figures@^1.0.11": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.11.tgz#4744e6db95288fea1dead779554859710a959a21" - integrity sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw== - -"@inquirer/figures@^1.0.13": - version "1.0.13" - resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.13.tgz#ad0afd62baab1c23175115a9b62f511b6a751e45" - integrity sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw== - -"@inquirer/input@^4.1.6": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-4.1.9.tgz#e93888d48c89bdb7f8e10bdd94572b636375749a" - integrity sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/type" "^3.0.6" - -"@inquirer/input@^4.2.1": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-4.2.2.tgz#98c420a3bff94ee19124f74a641cef2b1eb01b22" - integrity sha512-hqOvBZj/MhQCpHUuD3MVq18SSoDNHy7wEnQ8mtvs71K8OPZVXJinOzcvQna33dNYLYE4LkA9BlhAhK6MJcsVbw== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/type" "^3.0.8" - -"@inquirer/number@^3.0.17": - version "3.0.18" - resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-3.0.18.tgz#b5595c02061498e2753fdfe35d9abae14e9223aa" - integrity sha512-7exgBm52WXZRczsydCVftozFTrrwbG5ySE0GqUd2zLNSBXyIucs2Wnm7ZKLe/aUu6NUg9dg7Q80QIHCdZJiY4A== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/type" "^3.0.8" - -"@inquirer/number@^3.0.9": - version "3.0.12" - resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-3.0.12.tgz#e027d27425ee2a81a7ccb9fdc750129edd291067" - integrity sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/type" "^3.0.6" - -"@inquirer/password@^4.0.17": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-4.0.18.tgz#7500139016247163a6c115228fcafbb9cb448941" - integrity sha512-zXvzAGxPQTNk/SbT3carAD4Iqi6A2JS2qtcqQjsL22uvD+JfQzUrDEtPjLL7PLn8zlSNyPdY02IiQjzoL9TStA== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/type" "^3.0.8" - ansi-escapes "^4.3.2" - -"@inquirer/password@^4.0.9": - version "4.0.12" - resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-4.0.12.tgz#f1a663bc5cf88699643cf6c83626a1ae77e580b5" - integrity sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/type" "^3.0.6" - ansi-escapes "^4.3.2" - -"@inquirer/prompts@7.3.2": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-7.3.2.tgz#ad0879eb3bc783c19b78c420e5eeb18a09fc9b47" - integrity sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ== - dependencies: - "@inquirer/checkbox" "^4.1.2" - "@inquirer/confirm" "^5.1.6" - "@inquirer/editor" "^4.2.7" - "@inquirer/expand" "^4.0.9" - "@inquirer/input" "^4.1.6" - "@inquirer/number" "^3.0.9" - "@inquirer/password" "^4.0.9" - "@inquirer/rawlist" "^4.0.9" - "@inquirer/search" "^3.0.9" - "@inquirer/select" "^4.0.9" - -"@inquirer/prompts@7.8.0": - version "7.8.0" - resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-7.8.0.tgz#0bac9315e3ecd09ae21d1598b1c0df39a8b4a720" - integrity sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw== - dependencies: - "@inquirer/checkbox" "^4.2.0" - "@inquirer/confirm" "^5.1.14" - "@inquirer/editor" "^4.2.15" - "@inquirer/expand" "^4.0.17" - "@inquirer/input" "^4.2.1" - "@inquirer/number" "^3.0.17" - "@inquirer/password" "^4.0.17" - "@inquirer/rawlist" "^4.1.5" - "@inquirer/search" "^3.1.0" - "@inquirer/select" "^4.3.1" - -"@inquirer/rawlist@^4.0.9": - version "4.0.12" - resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-4.0.12.tgz#97b9540199590d2b197836ba3a5658addd406479" - integrity sha512-wNPJZy8Oc7RyGISPxp9/MpTOqX8lr0r+lCCWm7hQra+MDtYRgINv1hxw7R+vKP71Bu/3LszabxOodfV/uTfsaA== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/type" "^3.0.6" - yoctocolors-cjs "^2.1.2" - -"@inquirer/rawlist@^4.1.5": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-4.1.6.tgz#805e1c449dde2bdfd8bc7eca56e6fe40938a7dc7" - integrity sha512-KOZqa3QNr3f0pMnufzL7K+nweFFCCBs6LCXZzXDrVGTyssjLeudn5ySktZYv1XiSqobyHRYYK0c6QsOxJEhXKA== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/type" "^3.0.8" - yoctocolors-cjs "^2.1.2" - -"@inquirer/search@^3.0.9": - version "3.0.12" - resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-3.0.12.tgz#e86f91ea598ccb39caf9a17762b839a9b950e16d" - integrity sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/figures" "^1.0.11" - "@inquirer/type" "^3.0.6" - yoctocolors-cjs "^2.1.2" - -"@inquirer/search@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-3.1.1.tgz#f67a559c66043fe4fdc639c053578d34440b3c49" - integrity sha512-TkMUY+A2p2EYVY3GCTItYGvqT6LiLzHBnqsU1rJbrpXUijFfM6zvUx0R4civofVwFCmJZcKqOVwwWAjplKkhxA== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/figures" "^1.0.13" - "@inquirer/type" "^3.0.8" - yoctocolors-cjs "^2.1.2" - -"@inquirer/select@^4.0.9": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-4.1.1.tgz#0496b913514149171cf6351f0acb6d4243a39fdf" - integrity sha512-IUXzzTKVdiVNMA+2yUvPxWsSgOG4kfX93jOM4Zb5FgujeInotv5SPIJVeXQ+fO4xu7tW8VowFhdG5JRmmCyQ1Q== - dependencies: - "@inquirer/core" "^10.1.10" - "@inquirer/figures" "^1.0.11" - "@inquirer/type" "^3.0.6" - ansi-escapes "^4.3.2" - yoctocolors-cjs "^2.1.2" - -"@inquirer/select@^4.3.1": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-4.3.2.tgz#7ff8942fb052c9c92110c9c044c7abb9b4ba9497" - integrity sha512-nwous24r31M+WyDEHV+qckXkepvihxhnyIaod2MG7eCE6G0Zm/HUF6jgN8GXgf4U7AU6SLseKdanY195cwvU6w== - dependencies: - "@inquirer/core" "^10.2.0" - "@inquirer/figures" "^1.0.13" - "@inquirer/type" "^3.0.8" - ansi-escapes "^4.3.2" - yoctocolors-cjs "^2.1.2" - -"@inquirer/type@^3.0.6": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.6.tgz#2500e435fc2014c5250eec3279f42b70b64089bd" - integrity sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA== - -"@inquirer/type@^3.0.8": - version "3.0.8" - resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.8.tgz#efc293ba0ed91e90e6267f1aacc1c70d20b8b4e8" - integrity sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw== - -"@ioredis/commands@^1.1.1": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" - integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== - -"@isaacs/balanced-match@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" - integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== - -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== - dependencies: - "@isaacs/balanced-match" "^4.0.1" - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - 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" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.7.0" - jest-config "^29.7.0" - 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.4" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/create-cache-key-function@^30.0.0": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-30.0.5.tgz#6004225f7c143603bdb1a56099e9919cc056e581" - integrity sha512-W1kmkwPq/WTMQWgvbzWSCbXSqvjI6rkqBQCxuvYmd+g6o4b5gHP98ikfh/Ei0SKzHvWdI84TOXp0hRcbpr8Q0w== - dependencies: - "@jest/types" "30.0.5" - -"@jest/diff-sequences@30.0.1": - version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" - integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== - -"@jest/environment@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.2.0.tgz#1e673cdb8b93ded707cf6631b8353011460831fa" - integrity sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g== - dependencies: - "@jest/fake-timers" "30.2.0" - "@jest/types" "30.2.0" - "@types/node" "*" - jest-mock "30.2.0" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/expect-utils@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.2.0.tgz#4f95413d4748454fdb17404bf1141827d15e6011" - integrity sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA== - dependencies: - "@jest/get-type" "30.1.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.2.0.tgz#9a5968499bb8add2bbb09136f69f7df5ddbf3185" - integrity sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA== - dependencies: - expect "30.2.0" - jest-snapshot "30.2.0" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.2.0.tgz#0941ddc28a339b9819542495b5408622dc9e94ec" - integrity sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw== - dependencies: - "@jest/types" "30.2.0" - "@sinonjs/fake-timers" "^13.0.0" - "@types/node" "*" - jest-message-util "30.2.0" - jest-mock "30.2.0" - jest-util "30.2.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -"@jest/get-type@30.1.0": - version "30.1.0" - resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" - integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== - -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - -"@jest/globals@^30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.2.0.tgz#2f4b696d5862664b89c4ee2e49ae24d2bb7e0988" - integrity sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw== - dependencies: - "@jest/environment" "30.2.0" - "@jest/expect" "30.2.0" - "@jest/types" "30.2.0" - jest-mock "30.2.0" - -"@jest/pattern@30.0.1": - version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" - integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== - dependencies: - "@types/node" "*" - jest-regex-util "30.0.1" - -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - 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.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" - integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== - dependencies: - "@sinclair/typebox" "^0.34.0" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/snapshot-utils@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz#387858eb90c2f98f67bff327435a532ac5309fbe" - integrity sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug== - dependencies: - "@jest/types" "30.2.0" - chalk "^4.1.2" - graceful-fs "^4.2.11" - natural-compare "^1.4.0" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.2.0.tgz#54bef1a4510dcbd58d5d4de4fe2980a63077ef2a" - integrity sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA== - dependencies: - "@babel/core" "^7.27.4" - "@jest/types" "30.2.0" - "@jridgewell/trace-mapping" "^0.3.25" - babel-plugin-istanbul "^7.0.1" - chalk "^4.1.2" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.11" - jest-haste-map "30.2.0" - jest-regex-util "30.0.1" - jest-util "30.2.0" - micromatch "^4.0.8" - pirates "^4.0.7" - slash "^3.0.0" - write-file-atomic "^5.0.1" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" - integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== - dependencies: - "@jest/pattern" "30.0.1" - "@jest/schemas" "30.0.5" - "@types/istanbul-lib-coverage" "^2.0.6" - "@types/istanbul-reports" "^3.0.4" - "@types/node" "*" - "@types/yargs" "^17.0.33" - chalk "^4.1.2" - -"@jest/types@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.2.0.tgz#1c678a7924b8f59eafd4c77d56b6d0ba976d62b8" - integrity sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg== - dependencies: - "@jest/pattern" "30.0.1" - "@jest/schemas" "30.0.5" - "@types/istanbul-lib-coverage" "^2.0.6" - "@types/istanbul-reports" "^3.0.4" - "@types/node" "*" - "@types/yargs" "^17.0.33" - chalk "^4.1.2" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.12": - version "0.3.12" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" - integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/source-map@^0.3.3": - version "0.3.6" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" - integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/sourcemap-codec@^1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@jridgewell/trace-mapping@^0.3.28": - version "0.3.29" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" - integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@js-sdsl/ordered-map@^4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" - integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== - -"@lukeed/csprng@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" - integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== - -"@microsoft/tsdoc@^0.15.0": - version "0.15.1" - resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz#d4f6937353bc4568292654efb0a0e0532adbcba2" - integrity sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw== - -"@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz#9edec61b22c3082018a79f6d1c30289ddf3d9d11" - integrity sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw== - -"@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz#33677a275204898ad8acbf62734fc4dc0b6a4855" - integrity sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw== - -"@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz#19edf7cdc2e7063ee328403c1d895a86dd28f4bb" - integrity sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg== - -"@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz#94fb0543ba2e28766c3fc439cabbe0440ae70159" - integrity sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw== - -"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz#4a0609ab5fe44d07c9c60a11e4484d3c38bbd6e3" - integrity sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg== - -"@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz#0aa5502d547b57abfc4ac492de68e2006e417242" - integrity sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ== - -"@napi-rs/snappy-android-arm-eabi@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-android-arm-eabi/-/snappy-android-arm-eabi-7.2.2.tgz#85fee3ba198dad4b444b5f12bceebcf72db0d65e" - integrity sha512-H7DuVkPCK5BlAr1NfSU8bDEN7gYs+R78pSHhDng83QxRnCLmVIZk33ymmIwurmoA1HrdTxbkbuNl+lMvNqnytw== - -"@napi-rs/snappy-android-arm64@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-android-arm64/-/snappy-android-arm64-7.2.2.tgz#386219c790c729aa0ced7ac6e3ac846892c5dd6d" - integrity sha512-2R/A3qok+nGtpVK8oUMcrIi5OMDckGYNoBLFyli3zp8w6IArPRfg1yOfVUcHvpUDTo9T7LOS1fXgMOoC796eQw== - -"@napi-rs/snappy-darwin-arm64@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.2.2.tgz#32bd351c695fbf60c899b365fff4f64bcd8b612c" - integrity sha512-USgArHbfrmdbuq33bD5ssbkPIoT7YCXCRLmZpDS6dMDrx+iM7eD2BecNbOOo7/v1eu6TRmQ0xOzeQ6I/9FIi5g== - -"@napi-rs/snappy-darwin-x64@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-darwin-x64/-/snappy-darwin-x64-7.2.2.tgz#71a8fca67a1fccb6323b8520d8d90c6b5da7c577" - integrity sha512-0APDu8iO5iT0IJKblk2lH0VpWSl9zOZndZKnBYIc+ei1npw2L5QvuErFOTeTdHBtzvUHASB+9bvgaWnQo4PvTQ== - -"@napi-rs/snappy-freebsd-x64@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-freebsd-x64/-/snappy-freebsd-x64-7.2.2.tgz#42102cbdaac39748520c9518c4fd9d3241d83a80" - integrity sha512-mRTCJsuzy0o/B0Hnp9CwNB5V6cOJ4wedDTWEthsdKHSsQlO7WU9W1yP7H3Qv3Ccp/ZfMyrmG98Ad7u7lG58WXA== - -"@napi-rs/snappy-linux-arm-gnueabihf@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-linux-arm-gnueabihf/-/snappy-linux-arm-gnueabihf-7.2.2.tgz#7e26ff0d974153c8b87160e99f60259ee9e14f0d" - integrity sha512-v1uzm8+6uYjasBPcFkv90VLZ+WhLzr/tnfkZ/iD9mHYiULqkqpRuC8zvc3FZaJy5wLQE9zTDkTJN1IvUcZ+Vcg== - -"@napi-rs/snappy-linux-arm64-gnu@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-linux-arm64-gnu/-/snappy-linux-arm64-gnu-7.2.2.tgz#992b2c4162da8d1da37ba2988700365975c21f3a" - integrity sha512-LrEMa5pBScs4GXWOn6ZYXfQ72IzoolZw5txqUHVGs8eK4g1HR9HTHhb2oY5ySNaKakG5sOgMsb1rwaEnjhChmQ== - -"@napi-rs/snappy-linux-arm64-musl@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-linux-arm64-musl/-/snappy-linux-arm64-musl-7.2.2.tgz#808dbf14b8789a8be7ecebef7606311f4df694fd" - integrity sha512-3orWZo9hUpGQcB+3aTLW7UFDqNCQfbr0+MvV67x8nMNYj5eAeUtMmUE/HxLznHO4eZ1qSqiTwLbVx05/Socdlw== - -"@napi-rs/snappy-linux-x64-gnu@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-linux-x64-gnu/-/snappy-linux-x64-gnu-7.2.2.tgz#681bfa25d8ed38a0bbec56827aa2762146c7e035" - integrity sha512-jZt8Jit/HHDcavt80zxEkDpH+R1Ic0ssiVCoueASzMXa7vwPJeF4ZxZyqUw4qeSy7n8UUExomu8G8ZbP6VKhgw== - -"@napi-rs/snappy-linux-x64-musl@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-linux-x64-musl/-/snappy-linux-x64-musl-7.2.2.tgz#4607f33fd0ef95a11143deff0d465428abcaae5a" - integrity sha512-Dh96IXgcZrV39a+Tej/owcd9vr5ihiZ3KRix11rr1v0MWtVb61+H1GXXlz6+Zcx9y8jM1NmOuiIuJwkV4vZ4WA== - -"@napi-rs/snappy-win32-arm64-msvc@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-win32-arm64-msvc/-/snappy-win32-arm64-msvc-7.2.2.tgz#88eb45cfdc66bb3c6b51f10903b29848f42a5c6d" - integrity sha512-9No0b3xGbHSWv2wtLEn3MO76Yopn1U2TdemZpCaEgOGccz1V+a/1d16Piz3ofSmnA13HGFz3h9NwZH9EOaIgYA== - -"@napi-rs/snappy-win32-ia32-msvc@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-win32-ia32-msvc/-/snappy-win32-ia32-msvc-7.2.2.tgz#e336064445e3c764bc3464640584d191c0fcf6dc" - integrity sha512-QiGe+0G86J74Qz1JcHtBwM3OYdTni1hX1PFyLRo3HhQUSpmi13Bzc1En7APn+6Pvo7gkrcy81dObGLDSxFAkQQ== - -"@napi-rs/snappy-win32-x64-msvc@7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@napi-rs/snappy-win32-x64-msvc/-/snappy-win32-x64-msvc-7.2.2.tgz#4f598d3a5d50904d9f72433819f68b21eaec4f7d" - integrity sha512-a43cyx1nK0daw6BZxVcvDEXxKMFLSBSDTAhsFD0VqSKcC7MGUBMaqyoWUcMiI7LBSz4bxUmxDWKfCYzpEmeb3w== - -"@nestjs/cli@^11.0.10": - version "11.0.10" - resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-11.0.10.tgz#c5c3cb4c47d08fd8faead7bf0ddd3f82bec7ccee" - integrity sha512-4waDT0yGWANg0pKz4E47+nUrqIJv/UqrZ5wLPkCqc7oMGRMWKAaw1NDZ9rKsaqhqvxb2LfI5+uXOWr4yi94DOQ== - dependencies: - "@angular-devkit/core" "19.2.15" - "@angular-devkit/schematics" "19.2.15" - "@angular-devkit/schematics-cli" "19.2.15" - "@inquirer/prompts" "7.8.0" - "@nestjs/schematics" "^11.0.1" - ansis "4.1.0" - chokidar "4.0.3" - cli-table3 "0.6.5" - commander "4.1.1" - fork-ts-checker-webpack-plugin "9.1.0" - glob "11.0.3" - 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.8.3" - webpack "5.100.2" - webpack-node-externals "3.0.0" - -"@nestjs/common@^10.4.20": - version "10.4.20" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.20.tgz#db021ccfcae398c1cd6c8bc808a169285978d687" - integrity sha512-hxJxZF7jcKGuUzM9EYbuES80Z/36piJbiqmPy86mk8qOn5gglFebBTvcx7PWVbRNSb4gngASYnefBj/Y2HAzpQ== - dependencies: - uid "2.0.2" - file-type "20.4.1" - iterare "1.2.1" - tslib "2.8.1" - -"@nestjs/config@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.2.tgz#a2777a1fd2d0d594bab3953f50fbca95c14cce52" - integrity sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA== - dependencies: - dotenv "16.4.7" - dotenv-expand "12.0.1" - lodash "4.17.21" - -"@nestjs/core@^10.4.5": - version "10.4.17" - resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.4.17.tgz#6bddf1f25c4883023224e07c8ed133af092833d0" - integrity sha512-Tk4J5aS082NUYrsJEDLvGdU+kRnHAMdOvsA4j62fP5THO6fN6vqv6jWHfydhCiPGUCJWLT6m+mNIhETMhMAs+Q== - dependencies: - uid "2.0.2" - "@nuxtjs/opencollective" "0.3.2" - fast-safe-stringify "2.1.1" - iterare "1.2.1" - path-to-regexp "3.3.0" - tslib "2.8.1" - -"@nestjs/jwt@^11.0.1": - version "11.0.1" - resolved "https://registry.yarnpkg.com/@nestjs/jwt/-/jwt-11.0.1.tgz#3109ba23932330f4001db9e41e4374f2e41342df" - integrity sha512-HXSsc7SAnCnjA98TsZqrE7trGtHDnYXWp4Ffy6LwSmck1QvbGYdMzBquXofX5l6tIRpeY4Qidl2Ti2CVG77Pdw== - dependencies: - "@types/jsonwebtoken" "9.0.10" - jsonwebtoken "9.0.2" - -"@nestjs/mapped-types@2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.0.6.tgz#d2d8523709fd5d872a9b9e0c38162746e2a7f44e" - integrity sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ== - -"@nestjs/mapped-types@^2.0.5": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19" - integrity sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw== - -"@nestjs/passport@^11.0.5": - version "11.0.5" - resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-11.0.5.tgz#dd3e506c2fb7ddc80fd1321c01cc1a0ca6d6b609" - integrity sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ== - -"@nestjs/platform-express@^10.4.20": - version "10.4.20" - resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.4.20.tgz#7266acbdda550f230ed7e661ee60699270fd48f4" - integrity sha512-rh97mX3rimyf4xLMLHuTOBKe6UD8LOJ14VlJ1F/PTd6C6ZK9Ak6EHuJvdaGcSFQhd3ZMBh3I6CuujKGW9pNdIg== - dependencies: - body-parser "1.20.3" - cors "2.8.5" - express "4.21.2" - multer "2.0.2" - tslib "2.8.1" - -"@nestjs/schedule@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@nestjs/schedule/-/schedule-6.0.1.tgz#048b34e5c16d2d9fa14a9df2bfa2d3b7fa8ef168" - integrity sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q== - dependencies: - cron "4.3.3" - -"@nestjs/schematics@^11.0.1", "@nestjs/schematics@^11.0.9": - version "11.0.9" - resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-11.0.9.tgz#18a0d128c609be76410f5c7ea02680c8cd297113" - integrity sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g== - dependencies: - "@angular-devkit/core" "19.2.17" - "@angular-devkit/schematics" "19.2.17" - comment-json "4.4.1" - jsonc-parser "3.3.1" - pluralize "8.0.0" - -"@nestjs/swagger@^8.0.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-8.1.1.tgz#18d61a995b6b17759bca361474904d69c4406093" - integrity sha512-5Mda7H1DKnhKtlsb0C7PYshcvILv8UFyUotHzxmWh0G65Z21R3LZH/J8wmpnlzL4bmXIfr42YwbEwRxgzpJ5sQ== - dependencies: - "@microsoft/tsdoc" "^0.15.0" - "@nestjs/mapped-types" "2.0.6" - js-yaml "4.1.0" - lodash "4.17.21" - path-to-regexp "3.3.0" - swagger-ui-dist "5.18.2" - -"@nestjs/testing@^10.0.0": - version "10.4.17" - resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.4.17.tgz#f3a19309768261a380ac96ecddcef81231019e70" - integrity sha512-TV1fqSNqqXgp0W57jCAfhJRfsvpH+krd4RtYSa7Pu+aU9uB+xcMBn5M62G02aMU41DURVZXKNVbHsZb6meZqBQ== - dependencies: - tslib "2.8.1" - -"@nestjs/typeorm@^10.0.2": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@nestjs/typeorm/-/typeorm-10.0.2.tgz#25e3ec3c9a127b085c06fd7ea25f8690dba145c2" - integrity sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ== - dependencies: - uuid "9.0.1" - -"@noble/hashes@^1.1.5": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" - integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@nuxtjs/opencollective@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz#620ce1044f7ac77185e825e1936115bb38e2681c" - integrity sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA== - dependencies: - chalk "^4.1.0" - consola "^2.15.0" - node-fetch "^2.6.1" - -"@opentelemetry/api-logs@0.208.0", "@opentelemetry/api-logs@^0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz#56d3891010a1fa1cf600ba8899ed61b43ace511c" - integrity sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg== - dependencies: - "@opentelemetry/api" "^1.3.0" - -"@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.4.0", "@opentelemetry/api@^1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" - integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== - -"@opentelemetry/context-async-hooks@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz#5465f6fad6350f52cf9d95a92907a3a464d50644" - integrity sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ== - -"@opentelemetry/core@2.2.0", "@opentelemetry/core@^2.0.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.2.0.tgz#2f857d7790ff160a97db3820889b5f4cade6eaee" - integrity sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw== - dependencies: - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/exporter-logs-otlp-grpc@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.208.0.tgz#787034569b3030c92da236284b3a62f66e344e78" - integrity sha512-AmZDKFzbq/idME/yq68M155CJW1y056MNBekH9OZewiZKaqgwYN4VYfn3mXVPftYsfrCM2r4V6tS8H2LmfiDCg== - dependencies: - "@grpc/grpc-js" "^1.7.1" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-grpc-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/sdk-logs" "0.208.0" - -"@opentelemetry/exporter-logs-otlp-http@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.208.0.tgz#198d6e735e961a79352a3d032a28da295db802dc" - integrity sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg== - dependencies: - "@opentelemetry/api-logs" "0.208.0" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/sdk-logs" "0.208.0" - -"@opentelemetry/exporter-logs-otlp-proto@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.208.0.tgz#d7f7894f349b3b141d325fc9ab0a90bcad228893" - integrity sha512-Wy8dZm16AOfM7yddEzSFzutHZDZ6HspKUODSUJVjyhnZFMBojWDjSNgduyCMlw6qaxJYz0dlb0OEcb4Eme+BfQ== - dependencies: - "@opentelemetry/api-logs" "0.208.0" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-logs" "0.208.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - -"@opentelemetry/exporter-metrics-otlp-grpc@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.208.0.tgz#667a7db1d219977b78070fab2e3c9212349e7c25" - integrity sha512-YbEnk7jjYmvhIwp2xJGkEvdgnayrA2QSr28R1LR1klDPvCxsoQPxE6TokDbQpoCEhD3+KmJVEXfb4EeEQxjymg== - dependencies: - "@grpc/grpc-js" "^1.7.1" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/exporter-metrics-otlp-http" "0.208.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-grpc-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-metrics" "2.2.0" - -"@opentelemetry/exporter-metrics-otlp-http@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.208.0.tgz#95bebb720f25b700d6464919634bb3665eae7b34" - integrity sha512-QZ3TrI90Y0i1ezWQdvreryjY0a5TK4J9gyDLIyhLBwV+EQUvyp5wR7TFPKCAexD4TDSWM0t3ulQDbYYjVtzTyA== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-metrics" "2.2.0" - -"@opentelemetry/exporter-metrics-otlp-proto@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.208.0.tgz#09e923df3c6ab0d0f32ffdc213ec90f6d06f7af8" - integrity sha512-CvvVD5kRDmRB/uSMalvEF6kiamY02pB46YAqclHtfjJccNZFxbkkXkMMmcJ7NgBFa5THmQBNVQ2AHyX29nRxOw== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/exporter-metrics-otlp-http" "0.208.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-metrics" "2.2.0" - -"@opentelemetry/exporter-prometheus@0.208.0", "@opentelemetry/exporter-prometheus@^0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.208.0.tgz#a3d7545a64ff105391f47c89eb6101ea251bdc07" - integrity sha512-Rgws8GfIfq2iNWCD3G1dTD9xwYsCof1+tc5S5X0Ahdb5CrAPE+k5P70XCWHqrFFurVCcKaHLJ/6DjIBHWVfLiw== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-metrics" "2.2.0" - -"@opentelemetry/exporter-trace-otlp-grpc@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.208.0.tgz#c687b50ec8dd6f066128fec6caf0c58e3c87716c" - integrity sha512-E/eNdcqVUTAT7BC+e8VOw/krqb+5rjzYkztMZ/o+eyJl+iEY6PfczPXpwWuICwvsm0SIhBoh9hmYED5Vh5RwIw== - dependencies: - "@grpc/grpc-js" "^1.7.1" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-grpc-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - -"@opentelemetry/exporter-trace-otlp-http@0.208.0", "@opentelemetry/exporter-trace-otlp-http@^0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.208.0.tgz#3b58caa80ab31597472902d84fbc933d35cf4d59" - integrity sha512-jbzDw1q+BkwKFq9yxhjAJ9rjKldbt5AgIy1gmEIJjEV/WRxQ3B6HcLVkwbjJ3RcMif86BDNKR846KJ0tY0aOJA== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - -"@opentelemetry/exporter-trace-otlp-proto@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.208.0.tgz#e64d00d333b483299b89936102b1a5d075c48175" - integrity sha512-q844Jc3ApkZVdWYd5OAl+an3n1XXf3RWHa3Zgmnhw3HpsM3VluEKHckUUEqHPzbwDUx2lhPRVkqK7LsJ/CbDzA== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - -"@opentelemetry/exporter-zipkin@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.2.0.tgz#92cd6438fcd92d2202a58ae9540673c888b6741a" - integrity sha512-VV4QzhGCT7cWrGasBWxelBjqbNBbyHicWWS/66KoZoe9BzYwFB72SH2/kkc4uAviQlO8iwv2okIJy+/jqqEHTg== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/instrumentation-express@^0.57.0": - version "0.57.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.57.0.tgz#7a2a7e90a84ad6c109f42c15acabdc7f6646a412" - integrity sha512-HAdx/o58+8tSR5iW+ru4PHnEejyKrAy9fYFhlEI81o10nYxrGahnMAHWiSjhDC7UQSY3I4gjcPgSKQz4rm/asg== - dependencies: - "@opentelemetry/core" "^2.0.0" - "@opentelemetry/instrumentation" "^0.208.0" - "@opentelemetry/semantic-conventions" "^1.27.0" - -"@opentelemetry/instrumentation-fetch@^0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fetch/-/instrumentation-fetch-0.208.0.tgz#1bfa7593f0e4fac4933e287ce4dd030340281dd2" - integrity sha512-zgStoUfNF1xH9bCq539k1aeieKxPiAvBo5gKipQ9fIt+eJsFvqGcSzrrDX+OYgpIPW/IVNgWBoOw6zVmKwgNwQ== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/instrumentation" "0.208.0" - "@opentelemetry/sdk-trace-web" "2.2.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/instrumentation-http@^0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.208.0.tgz#64fcc02bfbc80eb3bbb91cd3c7e0e24c695f2bef" - integrity sha512-rhmK46DRWEbQQB77RxmVXGyjs6783crXCnFjYQj+4tDH/Kpv9Rbg3h2kaNyp5Vz2emF1f9HOQQvZoHzwMWOFZQ== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/instrumentation" "0.208.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - forwarded-parse "2.1.2" - -"@opentelemetry/instrumentation-nestjs-core@^0.55.0": - version "0.55.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.55.0.tgz#820391be7ed2b699b49fef55b78619832ac0e0ae" - integrity sha512-JFLNhbbEGnnQrMKOYoXx0nNk5N9cPeghu4xP/oup40a7VaSeYruyOiFbg9nkbS4ZQiI8aMuRqUT3Mo4lQjKEKg== - dependencies: - "@opentelemetry/instrumentation" "^0.208.0" - "@opentelemetry/semantic-conventions" "^1.30.0" - -"@opentelemetry/instrumentation-pg@^0.61.0": - version "0.61.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.61.0.tgz#c755d00dba640e229fe50f817423dcf3376957ab" - integrity sha512-UeV7KeTnRSM7ECHa3YscoklhUtTQPs6V6qYpG283AB7xpnPGCUCUfECFT9jFg6/iZOQTt3FHkB1wGTJCNZEvPw== - dependencies: - "@opentelemetry/core" "^2.0.0" - "@opentelemetry/instrumentation" "^0.208.0" - "@opentelemetry/semantic-conventions" "^1.34.0" - "@opentelemetry/sql-common" "^0.41.2" - "@types/pg" "8.15.6" - "@types/pg-pool" "2.0.6" - -"@opentelemetry/instrumentation-winston@^0.53.0": - version "0.53.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.53.0.tgz#fa842da65692dff9e1bc849a404aaf4c978b184a" - integrity sha512-yF9v0DphyG715er1HG1pbweNUSygvc22xw2s2Y8E8oaEMJo2/nH3Ww/8c4K6gdI/6xvi2unla1KQBCYN4uCo8w== - dependencies: - "@opentelemetry/api-logs" "^0.208.0" - "@opentelemetry/instrumentation" "^0.208.0" - -"@opentelemetry/instrumentation@0.208.0", "@opentelemetry/instrumentation@^0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz#d764f8e4329dad50804e2e98f010170c14c4ce8f" - integrity sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA== - dependencies: - "@opentelemetry/api-logs" "0.208.0" - import-in-the-middle "^2.0.0" - require-in-the-middle "^8.0.0" - -"@opentelemetry/otlp-exporter-base@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.208.0.tgz#1a932355628087555a317b7207637d4e893c1a5d" - integrity sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-transformer" "0.208.0" - -"@opentelemetry/otlp-grpc-exporter-base@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.208.0.tgz#c68cb5f4758d0fe4f158188ed5423076c6a229a1" - integrity sha512-fGvAg3zb8fC0oJAzfz7PQppADI2HYB7TSt/XoCaBJFi1mSquNUjtHXEoviMgObLAa1NRIgOC1lsV1OUKi+9+lQ== - dependencies: - "@grpc/grpc-js" "^1.7.1" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/otlp-exporter-base" "0.208.0" - "@opentelemetry/otlp-transformer" "0.208.0" - -"@opentelemetry/otlp-transformer@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.208.0.tgz#c59f48a569d17766d91c61807db7b04e4be490ac" - integrity sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ== - dependencies: - "@opentelemetry/api-logs" "0.208.0" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-logs" "0.208.0" - "@opentelemetry/sdk-metrics" "2.2.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - protobufjs "^7.3.0" - -"@opentelemetry/propagator-b3@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-2.2.0.tgz#fc1067b5fa618d06b6104d4c5b2a3c8ff7f09d55" - integrity sha512-9CrbTLFi5Ee4uepxg2qlpQIozoJuoAZU5sKMx0Mn7Oh+p7UrgCiEV6C02FOxxdYVRRFQVCinYR8Kf6eMSQsIsw== - dependencies: - "@opentelemetry/core" "2.2.0" - -"@opentelemetry/propagator-jaeger@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.2.0.tgz#c94b973320fffb44fba450978cbf23f68ced9cd3" - integrity sha512-FfeOHOrdhiNzecoB1jZKp2fybqmqMPJUXe2ZOydP7QzmTPYcfPeuaclTLYVhK3HyJf71kt8sTl92nV4YIaLaKA== - dependencies: - "@opentelemetry/core" "2.2.0" - -"@opentelemetry/resources@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.2.0.tgz#b90a950ad98551295b76ea8a0e7efe45a179badf" - integrity sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/sdk-logs@0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz#013494e23412c1594a694a358211cd150144c525" - integrity sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA== - dependencies: - "@opentelemetry/api-logs" "0.208.0" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/resources" "2.2.0" - -"@opentelemetry/sdk-metrics@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz#3824133f0d681d778aff0f52b02a87ec6750fc2d" - integrity sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/resources" "2.2.0" - -"@opentelemetry/sdk-node@^0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-node/-/sdk-node-0.208.0.tgz#213153f7a37c32df5088965e1ba25522ee2bbc76" - integrity sha512-pbAqpZ7zTMFuTf3YecYsecsto/mheuvnK2a/jgstsE5ynWotBjgF5bnz5500W9Xl2LeUfg04WMt63TWtAgzRMw== - dependencies: - "@opentelemetry/api-logs" "0.208.0" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/exporter-logs-otlp-grpc" "0.208.0" - "@opentelemetry/exporter-logs-otlp-http" "0.208.0" - "@opentelemetry/exporter-logs-otlp-proto" "0.208.0" - "@opentelemetry/exporter-metrics-otlp-grpc" "0.208.0" - "@opentelemetry/exporter-metrics-otlp-http" "0.208.0" - "@opentelemetry/exporter-metrics-otlp-proto" "0.208.0" - "@opentelemetry/exporter-prometheus" "0.208.0" - "@opentelemetry/exporter-trace-otlp-grpc" "0.208.0" - "@opentelemetry/exporter-trace-otlp-http" "0.208.0" - "@opentelemetry/exporter-trace-otlp-proto" "0.208.0" - "@opentelemetry/exporter-zipkin" "2.2.0" - "@opentelemetry/instrumentation" "0.208.0" - "@opentelemetry/propagator-b3" "2.2.0" - "@opentelemetry/propagator-jaeger" "2.2.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/sdk-logs" "0.208.0" - "@opentelemetry/sdk-metrics" "2.2.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - "@opentelemetry/sdk-trace-node" "2.2.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/sdk-trace-base@2.2.0", "@opentelemetry/sdk-trace-base@^2.0.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz#ddef9a0afd01a623d8625a3529f2137b05e67d0b" - integrity sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/resources" "2.2.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/sdk-trace-node@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.2.0.tgz#a2666a087d96fc31888169ad5f4cc1448b35cd8c" - integrity sha512-+OaRja3f0IqGG2kptVeYsrZQK9nKRSpfFrKtRBq4uh6nIB8bTBgaGvYQrQoRrQWQMA5dK5yLhDMDc0dvYvCOIQ== - dependencies: - "@opentelemetry/context-async-hooks" "2.2.0" - "@opentelemetry/core" "2.2.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - -"@opentelemetry/sdk-trace-web@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-web/-/sdk-trace-web-2.2.0.tgz#9b6a894cf166fc821d329b65e26a3fd008cbfef0" - integrity sha512-x/LHsDBO3kfqaFx5qSzBljJ5QHsRXrvS4MybBDy1k7Svidb8ZyIPudWVzj3s5LpPkYZIgi9e+7tdsNCnptoelw== - dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/sdk-trace-base" "2.2.0" - -"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.30.0", "@opentelemetry/semantic-conventions@^1.34.0": - version "1.36.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.36.0.tgz#149449bd4df4d0464220915ad4164121e0d75d4d" - integrity sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ== - -"@opentelemetry/sql-common@^0.41.2": - version "0.41.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz#7f4a14166cfd6c9ffe89096db1cc75eaf6443b19" - integrity sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ== - dependencies: - "@opentelemetry/core" "^2.0.0" - -"@paralleldrive/cuid2@^2.2.2": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz#7f91364d53b89e2c9cb9e02e8dd0f129e834455f" - integrity sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA== - dependencies: - "@noble/hashes" "^1.1.5" - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@pkgr/core@^0.2.9": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" - integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== - -"@scarf/scarf@=1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" - integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinclair/typebox@^0.34.0": - version "0.34.38" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" - integrity sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA== - -"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" - integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@sinonjs/fake-timers@^13.0.0": - version "13.0.5" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" - integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== - dependencies: - "@sinonjs/commons" "^3.0.1" - -"@smithy/abort-controller@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-4.0.2.tgz#36a23e8cc65fc03cacb6afa35dfbfd319c560c6b" - integrity sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/chunked-blob-reader-native@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz#33cbba6deb8a3c516f98444f65061784f7cd7f8c" - integrity sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig== - dependencies: - "@smithy/util-base64" "^4.0.0" - tslib "^2.6.2" - -"@smithy/chunked-blob-reader@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz#3f6ea5ff4e2b2eacf74cefd737aa0ba869b2e0f6" - integrity sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw== - dependencies: - tslib "^2.6.2" - -"@smithy/config-resolver@^4.0.0", "@smithy/config-resolver@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-4.1.0.tgz#de1043cbd75f05d99798b0fbcfdaf4b89b0f2f41" - integrity sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A== - dependencies: - "@smithy/node-config-provider" "^4.0.2" - "@smithy/types" "^4.2.0" - "@smithy/util-config-provider" "^4.0.0" - "@smithy/util-middleware" "^4.0.2" - tslib "^2.6.2" - -"@smithy/core@^3.0.0", "@smithy/core@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.2.0.tgz#613b15f76eab9a6be396b1d5453b6bc8f22ba99c" - integrity sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q== - dependencies: - "@smithy/middleware-serde" "^4.0.3" - "@smithy/protocol-http" "^5.1.0" - "@smithy/types" "^4.2.0" - "@smithy/util-body-length-browser" "^4.0.0" - "@smithy/util-middleware" "^4.0.2" - "@smithy/util-stream" "^4.2.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@smithy/credential-provider-imds@^4.0.0", "@smithy/credential-provider-imds@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz#1ec34a04842fa69996b151a695b027f0486c69a8" - integrity sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w== - dependencies: - "@smithy/node-config-provider" "^4.0.2" - "@smithy/property-provider" "^4.0.2" - "@smithy/types" "^4.2.0" - "@smithy/url-parser" "^4.0.2" - tslib "^2.6.2" - -"@smithy/eventstream-codec@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-4.0.2.tgz#d4d77699308a3dfeea1b2e87683845f5d8440bdb" - integrity sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ== - dependencies: - "@aws-crypto/crc32" "5.2.0" - "@smithy/types" "^4.2.0" - "@smithy/util-hex-encoding" "^4.0.0" - tslib "^2.6.2" - -"@smithy/eventstream-serde-browser@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.2.tgz#876f05491373ab217801c47b802601b8c09388d4" - integrity sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug== - dependencies: - "@smithy/eventstream-serde-universal" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/eventstream-serde-config-resolver@^4.0.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.0.tgz#4ab7a2575e9041a2df2179bce64619a4e632e4d3" - integrity sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/eventstream-serde-node@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.2.tgz#390306ff79edb0c607705f639d8c5a76caad4bf7" - integrity sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw== - dependencies: - "@smithy/eventstream-serde-universal" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/eventstream-serde-universal@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.2.tgz#9f45472fc4fe5fe5f7c22c33d90ec6fc0230d0ae" - integrity sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng== - dependencies: - "@smithy/eventstream-codec" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/fetch-http-handler@^5.0.0", "@smithy/fetch-http-handler@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz#9d3cacf044aa9573ab933f445ab95cddb284813d" - integrity sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ== - dependencies: - "@smithy/protocol-http" "^5.1.0" - "@smithy/querystring-builder" "^4.0.2" - "@smithy/types" "^4.2.0" - "@smithy/util-base64" "^4.0.0" - tslib "^2.6.2" - -"@smithy/hash-blob-browser@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.2.tgz#c51abe21684803f6eb5e43c4870e2af9e232a5cd" - integrity sha512-3g188Z3DyhtzfBRxpZjU8R9PpOQuYsbNnyStc/ZVS+9nVX1f6XeNOa9IrAh35HwwIZg+XWk8bFVtNINVscBP+g== - dependencies: - "@smithy/chunked-blob-reader" "^5.0.0" - "@smithy/chunked-blob-reader-native" "^4.0.0" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/hash-node@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-4.0.2.tgz#a34fe5a33b067d754ca63302b9791778f003e437" - integrity sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg== - dependencies: - "@smithy/types" "^4.2.0" - "@smithy/util-buffer-from" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@smithy/hash-stream-node@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-4.0.2.tgz#c9ee7d85710121268b7b487a7259375c949a3289" - integrity sha512-POWDuTznzbIwlEXEvvXoPMS10y0WKXK790soe57tFRfvf4zBHyzE529HpZMqmDdwG9MfFflnyzndUQ8j78ZdSg== - dependencies: - "@smithy/types" "^4.2.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@smithy/invalid-dependency@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz#e9b1c5e407d795f10a03afba90e37bccdc3e38f7" - integrity sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/is-array-buffer@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz#f84f0d9f9a36601a9ca9381688bd1b726fd39111" - integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== - dependencies: - tslib "^2.6.2" - -"@smithy/is-array-buffer@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz#55a939029321fec462bcc574890075cd63e94206" - integrity sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw== - dependencies: - tslib "^2.6.2" - -"@smithy/md5-js@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-4.0.2.tgz#ac8f05d2c845fde48d3fde805a04ec21030fd19b" - integrity sha512-Hc0R8EiuVunUewCse2syVgA2AfSRco3LyAv07B/zCOMa+jpXI9ll+Q21Nc6FAlYPcpNcAXqBzMhNs1CD/pP2bA== - dependencies: - "@smithy/types" "^4.2.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@smithy/middleware-content-length@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz#ff78658e8047ad7038f58478cf8713ee2f6ef647" - integrity sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A== - dependencies: - "@smithy/protocol-http" "^5.1.0" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/middleware-endpoint@^4.0.0", "@smithy/middleware-endpoint@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz#cbfe47c5632942c960dbcf71fb02fd0d9985444d" - integrity sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA== - dependencies: - "@smithy/core" "^3.2.0" - "@smithy/middleware-serde" "^4.0.3" - "@smithy/node-config-provider" "^4.0.2" - "@smithy/shared-ini-file-loader" "^4.0.2" - "@smithy/types" "^4.2.0" - "@smithy/url-parser" "^4.0.2" - "@smithy/util-middleware" "^4.0.2" - tslib "^2.6.2" - -"@smithy/middleware-retry@^4.0.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz#338ac1e025bbc6fd7b008152c4efa8bc0591acc9" - integrity sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg== - dependencies: - "@smithy/node-config-provider" "^4.0.2" - "@smithy/protocol-http" "^5.1.0" - "@smithy/service-error-classification" "^4.0.2" - "@smithy/smithy-client" "^4.2.0" - "@smithy/types" "^4.2.0" - "@smithy/util-middleware" "^4.0.2" - "@smithy/util-retry" "^4.0.2" - tslib "^2.6.2" - uuid "^9.0.1" - -"@smithy/middleware-serde@^4.0.0", "@smithy/middleware-serde@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz#b90ef1065ad9dc0b54c561fae73c8a5792d145e3" - integrity sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/middleware-stack@^4.0.0", "@smithy/middleware-stack@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz#ca7bc3eedc7c1349e2cf94e0dc92a68d681bef18" - integrity sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/node-config-provider@^4.0.0", "@smithy/node-config-provider@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz#017ba626828bced0fa588e795246e5468632f3ef" - integrity sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw== - dependencies: - "@smithy/property-provider" "^4.0.2" - "@smithy/shared-ini-file-loader" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/node-http-handler@^4.0.0", "@smithy/node-http-handler@^4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz#aa583d201c1ee968170b65a07f06d633c214b7a1" - integrity sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g== - dependencies: - "@smithy/abort-controller" "^4.0.2" - "@smithy/protocol-http" "^5.1.0" - "@smithy/querystring-builder" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/property-provider@^4.0.0", "@smithy/property-provider@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-4.0.2.tgz#4572c10415c9d4215f3df1530ba61b0319b17b55" - integrity sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/protocol-http@^5.0.0", "@smithy/protocol-http@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.1.0.tgz#ad34e336a95944785185234bebe2ec8dbe266936" - integrity sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/querystring-builder@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz#834cea95bf413ab417bf9c166d60fd80d2cb3016" - integrity sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q== - dependencies: - "@smithy/types" "^4.2.0" - "@smithy/util-uri-escape" "^4.0.0" - tslib "^2.6.2" - -"@smithy/querystring-parser@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz#d80c5afb740e12ad8b4d4f58415e402c69712479" - integrity sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/service-error-classification@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz#96740ed8be7ac5ad7d6f296d4ddf3f66444b8dcc" - integrity sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ== - dependencies: - "@smithy/types" "^4.2.0" - -"@smithy/shared-ini-file-loader@^4.0.0", "@smithy/shared-ini-file-loader@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz#15043f0516fe09ff4b22982bc5f644dc701ebae5" - integrity sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/signature-v4@^5.0.0": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-5.0.2.tgz#363854e946fbc5bc206ff82e79ada5d5c14be640" - integrity sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw== - dependencies: - "@smithy/is-array-buffer" "^4.0.0" - "@smithy/protocol-http" "^5.1.0" - "@smithy/types" "^4.2.0" - "@smithy/util-hex-encoding" "^4.0.0" - "@smithy/util-middleware" "^4.0.2" - "@smithy/util-uri-escape" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@smithy/smithy-client@^4.0.0", "@smithy/smithy-client@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-4.2.0.tgz#0c64cae4fb5bb4f26386e9b2c33fc9a3c24c9df3" - integrity sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw== - dependencies: - "@smithy/core" "^3.2.0" - "@smithy/middleware-endpoint" "^4.1.0" - "@smithy/middleware-stack" "^4.0.2" - "@smithy/protocol-http" "^5.1.0" - "@smithy/types" "^4.2.0" - "@smithy/util-stream" "^4.2.0" - tslib "^2.6.2" - -"@smithy/types@^4.0.0", "@smithy/types@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.2.0.tgz#e7998984cc54b1acbc32e6d4cf982c712e3d26b6" - integrity sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg== - dependencies: - tslib "^2.6.2" - -"@smithy/url-parser@^4.0.0", "@smithy/url-parser@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-4.0.2.tgz#a316f7d8593ffab796348bc5df96237833880713" - integrity sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ== - dependencies: - "@smithy/querystring-parser" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/util-base64@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-4.0.0.tgz#8345f1b837e5f636e5f8470c4d1706ae0c6d0358" - integrity sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg== - dependencies: - "@smithy/util-buffer-from" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@smithy/util-body-length-browser@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz#965d19109a4b1e5fe7a43f813522cce718036ded" - integrity sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA== - dependencies: - tslib "^2.6.2" - -"@smithy/util-body-length-node@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz#3db245f6844a9b1e218e30c93305bfe2ffa473b3" - integrity sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg== - dependencies: - tslib "^2.6.2" - -"@smithy/util-buffer-from@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz#6fc88585165ec73f8681d426d96de5d402021e4b" - integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== - dependencies: - "@smithy/is-array-buffer" "^2.2.0" - tslib "^2.6.2" - -"@smithy/util-buffer-from@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz#b23b7deb4f3923e84ef50c8b2c5863d0dbf6c0b9" - integrity sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug== - dependencies: - "@smithy/is-array-buffer" "^4.0.0" - tslib "^2.6.2" - -"@smithy/util-config-provider@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz#e0c7c8124c7fba0b696f78f0bd0ccb060997d45e" - integrity sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w== - dependencies: - tslib "^2.6.2" - -"@smithy/util-defaults-mode-browser@^4.0.0": - version "4.0.8" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz#77bc4590cdc928901b80f3482e79607a2cbcb150" - integrity sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ== - dependencies: - "@smithy/property-provider" "^4.0.2" - "@smithy/smithy-client" "^4.2.0" - "@smithy/types" "^4.2.0" - bowser "^2.11.0" - tslib "^2.6.2" - -"@smithy/util-defaults-mode-node@^4.0.0": - version "4.0.8" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz#123b517efe6434977139b341d1f64b5f1e743aac" - integrity sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA== - dependencies: - "@smithy/config-resolver" "^4.1.0" - "@smithy/credential-provider-imds" "^4.0.2" - "@smithy/node-config-provider" "^4.0.2" - "@smithy/property-provider" "^4.0.2" - "@smithy/smithy-client" "^4.2.0" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/util-endpoints@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz#6933a0d6d4a349523ef71ca9540c9c0b222b559e" - integrity sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ== - dependencies: - "@smithy/node-config-provider" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/util-hex-encoding@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz#dd449a6452cffb37c5b1807ec2525bb4be551e8d" - integrity sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw== - dependencies: - tslib "^2.6.2" - -"@smithy/util-middleware@^4.0.0", "@smithy/util-middleware@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-4.0.2.tgz#272f1249664e27068ef0d5f967a233bf7b77962c" - integrity sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ== - dependencies: - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/util-retry@^4.0.0", "@smithy/util-retry@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-4.0.2.tgz#9b64cf460d63555884e641721d19e3c0abff8ee6" - integrity sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg== - dependencies: - "@smithy/service-error-classification" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@smithy/util-stream@^4.0.0", "@smithy/util-stream@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-4.2.0.tgz#85f85516b0042726162bf619caa3358332195652" - integrity sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ== - dependencies: - "@smithy/fetch-http-handler" "^5.0.2" - "@smithy/node-http-handler" "^4.0.4" - "@smithy/types" "^4.2.0" - "@smithy/util-base64" "^4.0.0" - "@smithy/util-buffer-from" "^4.0.0" - "@smithy/util-hex-encoding" "^4.0.0" - "@smithy/util-utf8" "^4.0.0" - tslib "^2.6.2" - -"@smithy/util-uri-escape@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz#a96c160c76f3552458a44d8081fade519d214737" - integrity sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg== - dependencies: - tslib "^2.6.2" - -"@smithy/util-utf8@^2.0.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" - integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== - dependencies: - "@smithy/util-buffer-from" "^2.2.0" - tslib "^2.6.2" - -"@smithy/util-utf8@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-4.0.0.tgz#09ca2d9965e5849e72e347c130f2a29d5c0c863c" - integrity sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow== - dependencies: - "@smithy/util-buffer-from" "^4.0.0" - tslib "^2.6.2" - -"@smithy/util-waiter@^4.0.0": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-4.0.3.tgz#ec5605ec123493259ccbf1c0b5c1951b3360f43b" - integrity sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA== - dependencies: - "@smithy/abort-controller" "^4.0.2" - "@smithy/types" "^4.2.0" - tslib "^2.6.2" - -"@so-ric/colorspace@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@so-ric/colorspace/-/colorspace-1.1.6.tgz#62515d8b9f27746b76950a83bde1af812d91923b" - integrity sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw== - dependencies: - color "^5.0.2" - text-hex "1.0.x" - -"@sqltools/formatter@^1.2.5": - version "1.2.5" - resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" - integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== - -"@swc/core-darwin-arm64@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz#7638c073946f9297753ed9a2eb198d07b2336a24" - integrity sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ== - -"@swc/core-darwin-x64@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz#18061167378f0fb285e17818494bc6c89dd07551" - integrity sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng== - -"@swc/core-linux-arm-gnueabihf@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz#4c8062bd598049b5b9b0beb762e075e76b4c23c3" - integrity sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ== - -"@swc/core-linux-arm64-gnu@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz#7222d321197ea9304e387933e87d775849fc1ae6" - integrity sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw== - -"@swc/core-linux-arm64-musl@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz#51e7958deaf37edc212bd9dc0ea1476f151d2bea" - integrity sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ== - -"@swc/core-linux-x64-gnu@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz#3476beab93ab03e92844d955ca9d9289aa4a5993" - integrity sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA== - -"@swc/core-linux-x64-musl@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz#f4934b1e77e2a297909bb3ab977836205c36e5e0" - integrity sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q== - -"@swc/core-win32-arm64-msvc@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz#5084c107435cfc82d4d901bfb388dc319d38a236" - integrity sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw== - -"@swc/core-win32-ia32-msvc@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz#f8b2e28bc51b30467e316ed736a130c1324b9880" - integrity sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw== - -"@swc/core-win32-x64-msvc@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz#13883cf3c63bf11b787e28dcdf75ca0cc49efa83" - integrity sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q== - -"@swc/core@^1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.13.5.tgz#93874b831d3bd121560e6fcd688972b7fc7baa26" - integrity sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ== - dependencies: - "@swc/counter" "^0.1.3" - "@swc/types" "^0.1.24" - optionalDependencies: - "@swc/core-darwin-arm64" "1.13.5" - "@swc/core-darwin-x64" "1.13.5" - "@swc/core-linux-arm-gnueabihf" "1.13.5" - "@swc/core-linux-arm64-gnu" "1.13.5" - "@swc/core-linux-arm64-musl" "1.13.5" - "@swc/core-linux-x64-gnu" "1.13.5" - "@swc/core-linux-x64-musl" "1.13.5" - "@swc/core-win32-arm64-msvc" "1.13.5" - "@swc/core-win32-ia32-msvc" "1.13.5" - "@swc/core-win32-x64-msvc" "1.13.5" - -"@swc/counter@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" - integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== - -"@swc/jest@^0.2.39": - version "0.2.39" - resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.39.tgz#482bee0adb0726fab1487a4f902a278ec563a6b7" - integrity sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA== - dependencies: - "@jest/create-cache-key-function" "^30.0.0" - "@swc/counter" "^0.1.3" - jsonc-parser "^3.2.0" - -"@swc/types@^0.1.24": - version "0.1.25" - resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.25.tgz#b517b2a60feb37dd933e542d93093719e4cf1078" - integrity sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g== - dependencies: - "@swc/counter" "^0.1.3" - -"@tokenizer/inflate@^0.2.6": - version "0.2.7" - resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.2.7.tgz#32dd9dfc9abe457c89b3d9b760fc0690c85a103b" - integrity sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg== - dependencies: - debug "^4.4.0" - fflate "^0.8.2" - token-types "^6.0.0" - -"@tokenizer/token@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" - integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" - integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== - dependencies: - "@babel/types" "^7.20.7" - -"@types/body-parser@*": - version "1.19.5" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/cookie-parser@^1.4.9": - version "1.4.9" - resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.9.tgz#f0e79c766a58ee7369a52e7509b3840222f68ed2" - integrity sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g== - -"@types/cookiejar@^2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" - integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== - -"@types/eslint-scope@^3.7.7": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "9.6.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" - integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.6", "@types/estree@^1.0.8": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== - -"@types/express-serve-static-core@^5.0.0": - version "5.0.6" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz#41fec4ea20e9c7b22f024ab88a95c6bb288f51b8" - integrity sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*", "@types/express@^5.0.3": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956" - integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^5.0.0" - "@types/serve-static" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - -"@types/http-errors@*": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0", "@types/istanbul-reports@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^29.5.2": - version "29.5.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" - integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/jsonwebtoken@9.0.10": - version "9.0.10" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz#a7932a47177dcd4283b6146f3bd5c26d82647f09" - integrity sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA== - dependencies: - "@types/ms" "*" - "@types/node" "*" - -"@types/luxon@~3.7.0": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.7.1.tgz#ef51b960ff86801e4e2de80c68813a96e529d531" - integrity sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg== - -"@types/methods@^1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" - integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/ms@*": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" - integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== - -"@types/multer@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/multer/-/multer-2.0.0.tgz#db5f82136b619f5ce4d923b00034eb466c13acf4" - integrity sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw== - dependencies: - "@types/express" "*" - -"@types/node@*", "@types/node@>=13.7.0", "@types/node@^24.9.1": - version "24.9.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.9.1.tgz#b7360b3c789089e57e192695a855aa4f6981a53c" - integrity sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg== - dependencies: - undici-types "~7.16.0" - -"@types/oauth@*": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@types/oauth/-/oauth-0.9.6.tgz#fb5a278f6a826108a7467a01f856324e11e9ba4a" - integrity sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA== - dependencies: - "@types/node" "*" - -"@types/passport-github@^1.1.12": - version "1.1.12" - resolved "https://registry.yarnpkg.com/@types/passport-github/-/passport-github-1.1.12.tgz#85907077295c086af26c995f3865a2eb36bbda1f" - integrity sha512-VJpMEIH+cOoXB694QgcxuvWy2wPd1Oq3gqrg2Y9DMVBYs9TmH9L14qnqPDZsNMZKBDH+SvqRsGZj9SgHYeDgcA== - dependencies: - "@types/express" "*" - "@types/passport" "*" - "@types/passport-oauth2" "*" - -"@types/passport-google-oauth20@^2.0.16": - version "2.0.16" - resolved "https://registry.yarnpkg.com/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.16.tgz#9e39c1203d56496d89392538e6109626e253bc28" - integrity sha512-ayXK2CJ7uVieqhYOc6k/pIr5pcQxOLB6kBev+QUGS7oEZeTgIs1odDobXRqgfBPvXzl0wXCQHftV5220czZCPA== - dependencies: - "@types/express" "*" - "@types/passport" "*" - "@types/passport-oauth2" "*" - -"@types/passport-oauth2@*": - version "1.4.17" - resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz#d5d54339d44f6883d03e69dc0cc0e2114067abb4" - integrity sha512-ODiAHvso6JcWJ6ZkHHroVp05EHGhqQN533PtFNBkg8Fy5mERDqsr030AX81M0D69ZcaMvhF92SRckEk2B0HYYg== - dependencies: - "@types/express" "*" - "@types/oauth" "*" - "@types/passport" "*" - -"@types/passport@*": - version "1.0.17" - resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.17.tgz#718a8d1f7000ebcf6bbc0853da1bc8c4bc7ea5e6" - integrity sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg== - dependencies: - "@types/express" "*" - -"@types/pg-pool@2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.6.tgz#1376d9dc5aec4bb2ec67ce28d7e9858227403c77" - integrity sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ== - dependencies: - "@types/pg" "*" - -"@types/pg@*", "@types/pg@8.15.6": - version "8.15.6" - resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.15.6.tgz#4df7590b9ac557cbe5479e0074ec1540cbddad9b" - integrity sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ== - dependencies: - "@types/node" "*" - pg-protocol "*" - pg-types "^2.2.0" - -"@types/qs@*": - version "6.9.18" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2" - integrity sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/send@*": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" - integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - -"@types/stack-utils@^2.0.0", "@types/stack-utils@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - -"@types/superagent@^8.1.0": - version "8.1.9" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" - integrity sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ== - dependencies: - "@types/cookiejar" "^2.1.5" - "@types/methods" "^1.1.4" - "@types/node" "*" - form-data "^4.0.0" - -"@types/supertest@^6.0.3": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-6.0.3.tgz#d736f0e994b195b63e1c93e80271a2faf927388c" - integrity sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w== - dependencies: - "@types/methods" "^1.1.4" - "@types/superagent" "^8.1.0" - -"@types/triple-beam@^1.3.2": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" - integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== - -"@types/validator@^13.11.8": - version "13.15.0" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.15.0.tgz#d4643730536900190bb476a1dda0a4897c8881e2" - integrity sha512-nh7nrWhLr6CBq9ldtw0wx+z9wKnnv/uTVLA9g/3/TcOYxbpOSZE+MhKPmWqU+K0NvThjhv12uD8MuqijB0WzEA== - -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^17.0.33", "@types/yargs@^17.0.8": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@^8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz#9185b3eaa3b083d8318910e12d56c68b3c4f45b4" - integrity sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/type-utils" "8.32.1" - "@typescript-eslint/utils" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" - graphemer "^1.4.0" - ignore "^7.0.0" - natural-compare "^1.4.0" - ts-api-utils "^2.1.0" - -"@typescript-eslint/parser@^8.25.0": - version "8.31.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.0.tgz#5ec28823d06dd20ed5f67b61224823f12ccde095" - integrity sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw== - dependencies: - "@typescript-eslint/scope-manager" "8.31.0" - "@typescript-eslint/types" "8.31.0" - "@typescript-eslint/typescript-estree" "8.31.0" - "@typescript-eslint/visitor-keys" "8.31.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@8.31.0": - version "8.31.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz#48c7f7d729ea038e36cae0ff511e48c2412fb11c" - integrity sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw== - dependencies: - "@typescript-eslint/types" "8.31.0" - "@typescript-eslint/visitor-keys" "8.31.0" - -"@typescript-eslint/scope-manager@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz#9a6bf5fb2c5380e14fe9d38ccac6e4bbe17e8afc" - integrity sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA== - dependencies: - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" - -"@typescript-eslint/type-utils@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz#b9292a45f69ecdb7db74d1696e57d1a89514d21e" - integrity sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA== - dependencies: - "@typescript-eslint/typescript-estree" "8.32.1" - "@typescript-eslint/utils" "8.32.1" - debug "^4.3.4" - ts-api-utils "^2.1.0" - -"@typescript-eslint/types@8.31.0": - version "8.31.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.0.tgz#c48e20ec47a43b72747714f49ea9f7b38a4fa6c1" - integrity sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ== - -"@typescript-eslint/types@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.32.1.tgz#b19fe4ac0dc08317bae0ce9ec1168123576c1d4b" - integrity sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg== - -"@typescript-eslint/typescript-estree@8.31.0": - version "8.31.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz#9c7f84eff6ad23d63cf086c6e93af571cd561270" - integrity sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ== - dependencies: - "@typescript-eslint/types" "8.31.0" - "@typescript-eslint/visitor-keys" "8.31.0" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.0.1" - -"@typescript-eslint/typescript-estree@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz#9023720ca4ecf4f59c275a05b5fed69b1276face" - integrity sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg== - dependencies: - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.1.0" - -"@typescript-eslint/utils@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.32.1.tgz#4d6d5d29b9e519e9a85e9a74e9f7bdb58abe9704" - integrity sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA== - dependencies: - "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/typescript-estree" "8.32.1" - -"@typescript-eslint/visitor-keys@8.31.0": - version "8.31.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz#9a1a97ed16c60d4d1e7399b41c11a6d94ebc1ce5" - integrity sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ== - dependencies: - "@typescript-eslint/types" "8.31.0" - eslint-visitor-keys "^4.2.0" - -"@typescript-eslint/visitor-keys@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz#4321395cc55c2eb46036cbbb03e101994d11ddca" - integrity sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w== - dependencies: - "@typescript-eslint/types" "8.32.1" - eslint-visitor-keys "^4.2.0" - -"@ungap/structured-clone@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - -"@vue/compiler-core@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.21.tgz#5915b19273f0492336f0beb227aba86813e2c8a8" - integrity sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw== - dependencies: - "@babel/parser" "^7.28.3" - "@vue/shared" "3.5.21" - entities "^4.5.0" - estree-walker "^2.0.2" - source-map-js "^1.2.1" - -"@vue/compiler-dom@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz#26126447fe1e1d16c8cbac45b26e66b3f7175f65" - integrity sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ== - dependencies: - "@vue/compiler-core" "3.5.21" - "@vue/shared" "3.5.21" - -"@vue/compiler-sfc@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz#e48189ef3ffe334c864c2625389ebe3bb4fa41eb" - integrity sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ== - dependencies: - "@babel/parser" "^7.28.3" - "@vue/compiler-core" "3.5.21" - "@vue/compiler-dom" "3.5.21" - "@vue/compiler-ssr" "3.5.21" - "@vue/shared" "3.5.21" - estree-walker "^2.0.2" - magic-string "^0.30.18" - postcss "^8.5.6" - source-map-js "^1.2.1" - -"@vue/compiler-ssr@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz#f351c27aa5c075faa609596b2269c53df0df3aa1" - integrity sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w== - dependencies: - "@vue/compiler-dom" "3.5.21" - "@vue/shared" "3.5.21" - -"@vue/reactivity@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.21.tgz#34d4532c325876cf5481206060a3d525862c8ac5" - integrity sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA== - dependencies: - "@vue/shared" "3.5.21" - -"@vue/runtime-core@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.21.tgz#d97a4e7223a99644129f95c7d8318a7e92f255e4" - integrity sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA== - dependencies: - "@vue/reactivity" "3.5.21" - "@vue/shared" "3.5.21" - -"@vue/runtime-dom@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz#a3d35d53320abe8462c3bf2a469f729d8c9f78ff" - integrity sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w== - dependencies: - "@vue/reactivity" "3.5.21" - "@vue/runtime-core" "3.5.21" - "@vue/shared" "3.5.21" - csstype "^3.1.3" - -"@vue/server-renderer@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.21.tgz#1d0be5059a0c10f2c0483eef71ebf5bfd21a8b49" - integrity sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA== - dependencies: - "@vue/compiler-ssr" "3.5.21" - "@vue/shared" "3.5.21" - -"@vue/shared@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.21.tgz#505edb122629d1979f70a2a65ca0bd4050dc2e54" - integrity sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw== - -"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" - integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== - dependencies: - "@webassemblyjs/helper-numbers" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - -"@webassemblyjs/floating-point-hex-parser@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" - integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== - -"@webassemblyjs/helper-api-error@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" - integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== - -"@webassemblyjs/helper-buffer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" - integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== - -"@webassemblyjs/helper-numbers@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" - integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.13.2" - "@webassemblyjs/helper-api-error" "1.13.2" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" - integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== - -"@webassemblyjs/helper-wasm-section@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" - integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/wasm-gen" "1.14.1" - -"@webassemblyjs/ieee754@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" - integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" - integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" - integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== - -"@webassemblyjs/wasm-edit@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" - integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/helper-wasm-section" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-opt" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - "@webassemblyjs/wast-printer" "1.14.1" - -"@webassemblyjs/wasm-gen@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" - integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wasm-opt@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" - integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - -"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" - integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-api-error" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wast-printer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" - integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@xtuc/long" "4.2.2" - -"@willsoto/nestjs-prometheus@^6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@willsoto/nestjs-prometheus/-/nestjs-prometheus-6.0.2.tgz#d603764a923848442ed092411716c0bf211de01f" - integrity sha512-ePyLZYdIrOOdlOWovzzMisIgviXqhPVzFpSMKNNhn6xajhRHeBsjAzSdpxZTc6pnjR9hw1lNAHyKnKl7lAPaVg== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -"@zxing/text-encoding@0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" - integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-attributes@^1.9.5: - version "1.9.5" - resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" - integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== - -acorn-import-phases@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" - integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.4.1, acorn@^8.8.2: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - -agent-base@^7.1.2: - version "7.1.3" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" - integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== - -ajv-formats@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" - integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== - dependencies: - ajv "^8.0.0" - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@8.17.1, ajv@^8.0.0, ajv@^8.9.0: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-colors@4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" - integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0, ansi-styles@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -ansis@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansis/-/ansis-4.1.0.tgz#cd43ecd3f814f37223e518291c0e0b04f2915a0d" - integrity sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w== - -ansis@^3.17.0: - version "3.17.0" - resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7" - integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg== - -anymatch@^3.0.3, anymatch@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -app-root-path@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" - integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== - -append-field@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" - integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-timsort@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926" - integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ== - -arrify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== - -asap@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - -async-exit-hook@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3" - integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw== - -async@^3.2.3, async@^3.2.4: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -aws4@^1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" - integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== - -axios@^1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687" - integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.4" - proxy-from-env "^1.1.0" - -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-istanbul@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz#d8b518c8ea199364cf84ccc82de89740236daf92" - integrity sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-instrument "^6.0.2" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" - integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - -babel-preset-current-node-syntax@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" - integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.0, base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -base64url@3.x.x: - version "3.0.1" - resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" - integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== - -baseline-browser-mapping@^2.8.25: - version "2.8.29" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.29.tgz#d8800b71399c783cb1bf2068c2bcc3b6cfd7892c" - integrity sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA== - -bignumber.js@^9.0.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.0.tgz#bdba7e2a4c1a2eba08290e8dcad4f36393c92acd" - integrity sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA== - -bintrees@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" - integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== - -bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -block-stream2@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/block-stream2/-/block-stream2-2.1.0.tgz#ac0c5ef4298b3857796e05be8ebed72196fa054b" - integrity sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg== - dependencies: - readable-stream "^3.4.0" - -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -bowser@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browser-or-node@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-2.1.1.tgz#738790b3a86a8fc020193fa581273fbe65eaea0f" - integrity sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg== - -browserslist@^4.24.0, browserslist@^4.26.3: - version "4.28.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.0.tgz#9cefece0a386a17a3cd3d22ebf67b9deca1b5929" - integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== - dependencies: - baseline-browser-mapping "^2.8.25" - caniuse-lite "^1.0.30001754" - electron-to-chromium "^1.5.249" - node-releases "^2.0.27" - update-browserslist-db "^1.1.4" - -bs-logger@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -btoa@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" - integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g== - -buffer-crc32@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" - integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== - -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -bull@^4.12.2: - version "4.16.5" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.16.5.tgz#855a17e7880862a231dac3c8a6392c77668ce49d" - integrity sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ== - dependencies: - cron-parser "^4.9.0" - get-port "^5.1.1" - ioredis "^5.3.2" - lodash "^4.17.21" - msgpackr "^1.11.2" - semver "^7.5.2" - uuid "^8.3.0" - -busboy@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" - integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== - dependencies: - streamsearch "^1.1.0" - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001754: - version "1.0.30001755" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz#c01cfb1c30f5acf1229391666ec03492f4c332ff" - integrity sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA== - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -chardet@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-2.1.0.tgz#1007f441a1ae9f9199a4a67f6e978fb0aa9aa3fe" - integrity sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA== - -chokidar@4.0.3, chokidar@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== - dependencies: - readdirp "^4.0.1" - -chrome-trace-event@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" - integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -ci-info@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" - integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== - -cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" - integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== - -class-transformer@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" - integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== - -class-validator@^0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.2.tgz#a3de95edd26b703e89c151a2023d3c115030340d" - integrity sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw== - dependencies: - "@types/validator" "^13.11.8" - libphonenumber-js "^1.11.1" - validator "^13.9.0" - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.5.0: - version "2.9.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" - integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== - -cli-table3@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" - integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -cli-width@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" - integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -cluster-key-slot@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" - integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-convert@^3.0.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-3.1.2.tgz#cef9e0fd4cb90b07c14697b3fa70af9d7f4870f1" - integrity sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg== - dependencies: - color-name "^2.0.0" - -color-name@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-2.0.2.tgz#85054825a23e6d6f81d3503f660c4c4a2a15f04f" - integrity sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-2.1.2.tgz#db1dd52414cc9037ada8fa7d936b8e9f6c3366c9" - integrity sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA== - dependencies: - color-name "^2.0.0" - -color@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/color/-/color-5.0.2.tgz#712ec894007ab27b37207732d182784e001b4a3d" - integrity sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA== - dependencies: - color-convert "^3.0.1" - color-string "^2.0.0" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -comment-json@4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.4.1.tgz#0757e3ba31a9e56f3f6e00bdaae114384ac8bcf3" - integrity sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg== - dependencies: - array-timsort "^1.0.3" - core-util-is "^1.0.3" - esprima "^4.0.1" - -component-emitter@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" - integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concat-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" - integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.0.2" - typedarray "^0.0.6" - -consola@^2.15.0: - version "2.15.3" - resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" - integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie-parser@^1.4.6: - version "1.4.7" - resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.7.tgz#e2125635dfd766888ffe90d60c286404fa0e7b26" - integrity sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw== - dependencies: - cookie "0.7.2" - cookie-signature "1.0.6" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== - -cookie@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== - -cookiejar@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" - integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== - -core-util-is@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cors@2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cosmiconfig@^8.2.0: - version "8.3.6" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" - integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== - dependencies: - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - path-type "^4.0.0" - -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cron-parser@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5" - integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q== - dependencies: - luxon "^3.2.1" - -cron@4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/cron/-/cron-4.3.3.tgz#d37cfcbc73ba34a50d9d9ce9b653ae60837377d7" - integrity sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw== - dependencies: - "@types/luxon" "~3.7.0" - luxon "~3.7.0" - -cross-spawn@^7.0.3, cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -csstype@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - -data-uri-to-buffer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" - integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== - -date-fns@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" - integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== - -dayjs@^1.11.13: - version "1.11.13" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" - integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -decode-uri-component@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -dedent@^1.0.0, dedent@^1.6.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" - integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -denque@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" - integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-libc@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" - integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -dezalgo@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" - integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== - dependencies: - asap "^2.0.0" - wrappy "1" - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dotenv-expand@12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-12.0.1.tgz#44bdfa204a368100689ec35d7385755f599ceeb1" - integrity sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ== - dependencies: - dotenv "^16.4.5" - -dotenv@16.4.7: - version "16.4.7" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" - integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== - -dotenv@^16.4.5, dotenv@^16.4.7: - version "16.5.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692" - integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.5.249: - version "1.5.255" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.255.tgz#fe9294ce172241eb50733bc00f2bd00d9c1e4ec7" - integrity sha512-Z9oIp4HrFF/cZkDPMpz2XSuVpc1THDpT4dlmATFlJUIBVCy9Vap5/rIXsASP1CscBacBqhabwh8vLctqBwEerQ== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.2, enhanced-resolve@^5.17.3, enhanced-resolve@^5.7.0: - version "5.18.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz#9b5f4c5c076b8787c78fe540392ce76a88855b44" - integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -entities@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-module-lexer@^1.2.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" - integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-prettier@^10.1.8: - version "10.1.8" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97" - integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w== - -eslint-plugin-prettier@^5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz#9d61c4ea11de5af704d4edf108c82ccfa7f2e61c" - integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg== - dependencies: - prettier-linter-helpers "^1.0.0" - synckit "^0.11.7" - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" - integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint-visitor-keys@^4.2.0, eslint-visitor-keys@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" - integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== - -eslint@^9.37.0: - version "9.37.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.37.0.tgz#ac0222127f76b09c0db63036f4fe289562072d74" - integrity sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig== - dependencies: - "@eslint-community/eslint-utils" "^4.8.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.21.0" - "@eslint/config-helpers" "^0.4.0" - "@eslint/core" "^0.16.0" - "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.37.0" - "@eslint/plugin-kit" "^0.4.0" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.2" - "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^8.4.0" - eslint-visitor-keys "^4.2.1" - espree "^10.4.0" - esquery "^1.5.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - -espree@^10.0.1, espree@^10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" - integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== - dependencies: - acorn "^8.15.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.1" - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -estree-walker@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" - integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-30.2.0.tgz#d4013bed267013c14bc1199cec8aa57cee9b5869" - integrity sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw== - dependencies: - "@jest/expect-utils" "30.2.0" - "@jest/get-type" "30.1.0" - jest-matcher-utils "30.2.0" - jest-message-util "30.2.0" - jest-mock "30.2.0" - jest-util "30.2.0" - -expect@^29.0.0, expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - 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" - -express@4.21.2: - version "4.21.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.12" - proxy-addr "~2.0.7" - qs "6.13.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -external-editor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" - integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== - -fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - -fast-uri@^3.0.1: - version "3.0.6" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" - integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== - -fast-xml-parser@4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" - integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== - dependencies: - strnum "^1.0.5" - -fast-xml-parser@^4.4.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz#c54d6b35aa0f23dc1ea60b6c884340c006dc6efb" - integrity sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig== - dependencies: - strnum "^1.1.1" - -fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0, fb-watchman@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - -fetch-blob@^3.1.2, fetch-blob@^3.1.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" - integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== - dependencies: - node-domexception "^1.0.0" - web-streams-polyfill "^3.0.3" - -fflate@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" - integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== - -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== - dependencies: - flat-cache "^4.0.0" - -file-type@20.4.1: - version "20.4.1" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-20.4.1.tgz#8a58cf0922c6098af0ca5d84d5cf859c0c0f56a5" - integrity sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ== - dependencies: - "@tokenizer/inflate" "^0.2.6" - strtok3 "^10.2.0" - token-types "^6.0.0" - uint8array-extras "^1.4.0" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== - -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.4" - -flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - -follow-redirects@^1.15.6: - version "1.15.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" - integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== - -for-each@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" - integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== - dependencies: - is-callable "^1.2.7" - -foreground-child@^3.1.0, foreground-child@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" - integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== - dependencies: - cross-spawn "^7.0.6" - signal-exit "^4.0.1" - -fork-ts-checker-webpack-plugin@9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz#433481c1c228c56af111172fcad7df79318c915a" - integrity sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q== - dependencies: - "@babel/code-frame" "^7.16.7" - chalk "^4.1.2" - chokidar "^4.0.1" - cosmiconfig "^8.2.0" - deepmerge "^4.2.2" - fs-extra "^10.0.0" - memfs "^3.4.1" - minimatch "^3.0.4" - node-abort-controller "^3.0.1" - schema-utils "^3.1.1" - semver "^7.3.5" - tapable "^2.2.1" - -form-data@^4.0.0, form-data@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" - integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" - -formdata-polyfill@^4.0.10: - version "4.0.10" - resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" - integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== - dependencies: - fetch-blob "^3.1.2" - -formidable@^3.5.4: - version "3.5.4" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.4.tgz#ac9a593b951e829b3298f21aa9a2243932f32ed9" - integrity sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug== - dependencies: - "@paralleldrive/cuid2" "^2.2.2" - dezalgo "^1.0.4" - once "^1.4.0" - -forwarded-parse@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/forwarded-parse/-/forwarded-parse-2.1.2.tgz#08511eddaaa2ddfd56ba11138eee7df117a09325" - integrity sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-monkey@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" - integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2, fsevents@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gaxios@^6.0.0, gaxios@^6.1.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.7.1.tgz#ebd9f7093ede3ba502685e73390248bb5b7f71fb" - integrity sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ== - dependencies: - extend "^3.0.2" - https-proxy-agent "^7.0.1" - is-stream "^2.0.0" - node-fetch "^2.6.9" - uuid "^9.0.1" - -gaxios@^7.0.0, gaxios@^7.0.0-rc.4: - version "7.1.3" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-7.1.3.tgz#c5312f4254abc1b8ab53aef30c22c5229b80b1e1" - integrity sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ== - dependencies: - extend "^3.0.2" - https-proxy-agent "^7.0.1" - node-fetch "^3.3.2" - rimraf "^5.0.1" - -gcp-metadata@^6.1.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.1.tgz#f65aa69f546bc56e116061d137d3f5f90bdec494" - integrity sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A== - dependencies: - gaxios "^6.1.1" - google-logging-utils "^0.0.2" - json-bigint "^1.0.0" - -gcp-metadata@^8.0.0: - version "8.1.2" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-8.1.2.tgz#e62e3373ddf41fc727ccc31c55c687b798bee898" - integrity sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg== - dependencies: - gaxios "^7.0.0" - google-logging-utils "^1.0.0" - json-bigint "^1.0.0" - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-port@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" - integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== - -get-proto@^1.0.0, get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.3.tgz#9d8087e6d72ddb3c4707b1d2778f80ea3eaefcd6" - integrity sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA== - dependencies: - foreground-child "^3.3.1" - jackspeak "^4.1.1" - minimatch "^10.0.3" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^2.0.0" - -glob@^10.3.7, glob@^10.4.5: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" - integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== - -google-auth-library@^10.1.0, google-auth-library@^10.2.0: - version "10.5.0" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-10.5.0.tgz#3f0ebd47173496b91d2868f572bb8a8180c4b561" - integrity sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w== - dependencies: - base64-js "^1.3.0" - ecdsa-sig-formatter "^1.0.11" - gaxios "^7.0.0" - gcp-metadata "^8.0.0" - google-logging-utils "^1.0.0" - gtoken "^8.0.0" - jws "^4.0.0" - -google-auth-library@^9.0.0: - version "9.15.1" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.15.1.tgz#0c5d84ed1890b2375f1cd74f03ac7b806b392928" - integrity sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng== - dependencies: - base64-js "^1.3.0" - ecdsa-sig-formatter "^1.0.11" - gaxios "^6.1.1" - gcp-metadata "^6.1.0" - gtoken "^7.0.0" - jws "^4.0.0" - -google-logging-utils@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/google-logging-utils/-/google-logging-utils-0.0.2.tgz#5fd837e06fa334da450433b9e3e1870c1594466a" - integrity sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ== - -google-logging-utils@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/google-logging-utils/-/google-logging-utils-1.1.2.tgz#57cd7ef5e679d44c192cc042b767f989cdfca1f5" - integrity sha512-YsFPGVgDFf4IzSwbwIR0iaFJQFmR5Jp7V1WuYSjuRgAm9yWqsMhKE9YPlL+wvFLnc/wMiFV4SQUD9Y/JMpxIxQ== - -googleapis-common@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/googleapis-common/-/googleapis-common-8.0.0.tgz#7a3ea3aa3863c9d3e7635070314d639dadfa164a" - integrity sha512-66if47It7y+Sab3HMkwEXx1kCq9qUC9px8ZXoj1CMrmLmUw81GpbnsNlXnlyZyGbGPGcj+tDD9XsZ23m7GLaJQ== - dependencies: - extend "^3.0.2" - gaxios "^7.0.0-rc.4" - google-auth-library "^10.1.0" - qs "^6.7.0" - url-template "^2.0.8" - -googleapis@^165.0.0: - version "165.0.0" - resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-165.0.0.tgz#7e9ee403d86e3e971ba22db2471a97b6cf680ac8" - integrity sha512-uXA/Z779zTkW7NUNZqjJz6878inQyU1PIpxisZDezJaR7wfKPP3d/BNeGLDNbO2wnkMpZ6bmpeshWNRR/fjqmw== - dependencies: - google-auth-library "^10.2.0" - googleapis-common "^8.0.0" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -gtoken@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26" - integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw== - dependencies: - gaxios "^6.0.0" - jws "^4.0.0" - -gtoken@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-8.0.0.tgz#d67a0e346dd441bfb54ad14040ddc3b632886575" - integrity sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw== - dependencies: - gaxios "^7.0.0" - jws "^4.0.0" - -handlebars@^4.7.8: - version "4.7.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" - integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.2" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -https-proxy-agent@^7.0.1: - version "7.0.6" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" - integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== - dependencies: - agent-base "^7.1.2" - debug "4" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24, iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ieee754@^1.1.13, ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.2.0: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -ignore@^7.0.0: - version "7.0.5" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" - integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== - -import-fresh@^3.2.1, import-fresh@^3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-in-the-middle@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz#295948cee94d0565314824c6bd75379d13e5b1a5" - integrity sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A== - dependencies: - acorn "^8.14.0" - acorn-import-attributes "^1.9.5" - cjs-module-lexer "^1.2.2" - module-details-from-path "^1.0.3" - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ioredis@^5.3.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.6.1.tgz#1ed7dc9131081e77342503425afceaf7357ae599" - integrity sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA== - dependencies: - "@ioredis/commands" "^1.1.1" - cluster-key-slot "^1.1.0" - debug "^4.3.4" - 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" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== - -is-arguments@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b" - integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA== - dependencies: - call-bound "^1.0.2" - has-tostringtag "^1.0.2" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.16.0: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-generator-function@^1.0.7: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" - integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== - dependencies: - call-bound "^1.0.3" - get-proto "^1.0.0" - has-tostringtag "^1.0.2" - safe-regex-test "^1.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-regex@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" - integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== - dependencies: - call-bound "^1.0.2" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-typed-array@^1.1.14, is-typed-array@^1.1.3: - version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" - integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== - dependencies: - which-typed-array "^1.1.16" - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-wsl@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== - dependencies: - "@babel/core" "^7.23.9" - "@babel/parser" "^7.23.9" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -iterare@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" - integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== - -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -jackspeak@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.1.tgz#96876030f450502047fc7e8c7fcf8ce8124e43ae" - integrity sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.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.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - create-jest "^29.7.0" - exit "^0.1.2" - import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - yargs "^17.3.1" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.0" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - 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.4" - parse-json "^5.2.0" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.2.0.tgz#e3ec3a6ea5c5747f605c9e874f83d756cba36825" - integrity sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A== - dependencies: - "@jest/diff-sequences" "30.0.1" - "@jest/get-type" "30.1.0" - chalk "^4.1.2" - pretty-format "30.2.0" - -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.2.0.tgz#808e3889f288603ac70ff0ac047598345a66022e" - integrity sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw== - dependencies: - "@jest/types" "30.2.0" - "@types/node" "*" - anymatch "^3.1.3" - fb-watchman "^2.0.2" - graceful-fs "^4.2.11" - jest-regex-util "30.0.1" - jest-util "30.2.0" - jest-worker "30.2.0" - micromatch "^4.0.8" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.3" - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-junit@^16.0.0: - version "16.0.0" - resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-16.0.0.tgz#d838e8c561cf9fdd7eb54f63020777eee4136785" - integrity sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ== - dependencies: - mkdirp "^1.0.4" - strip-ansi "^6.0.1" - uuid "^8.3.2" - xml "^1.0.1" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-matcher-utils@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz#69a0d4c271066559ec8b0d8174829adc3f23a783" - integrity sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg== - dependencies: - "@jest/get-type" "30.1.0" - chalk "^4.1.2" - jest-diff "30.2.0" - pretty-format "30.2.0" - -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-message-util@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.2.0.tgz#fc97bf90d11f118b31e6131e2b67fc4f39f92152" - integrity sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@jest/types" "30.2.0" - "@types/stack-utils" "^2.0.3" - chalk "^4.1.2" - graceful-fs "^4.2.11" - micromatch "^4.0.8" - pretty-format "30.2.0" - slash "^3.0.0" - stack-utils "^2.0.6" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.2.0.tgz#69f991614eeb4060189459d3584f710845bff45e" - integrity sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw== - dependencies: - "@jest/types" "30.2.0" - "@types/node" "*" - jest-util "30.2.0" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@30.0.1: - version "30.0.1" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" - integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - 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" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - 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" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - 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" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - 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" - -jest-snapshot@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.2.0.tgz#266fbbb4b95fc4665ce6f32f1f38eeb39f4e26d0" - integrity sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA== - dependencies: - "@babel/core" "^7.27.4" - "@babel/generator" "^7.27.5" - "@babel/plugin-syntax-jsx" "^7.27.1" - "@babel/plugin-syntax-typescript" "^7.27.1" - "@babel/types" "^7.27.3" - "@jest/expect-utils" "30.2.0" - "@jest/get-type" "30.1.0" - "@jest/snapshot-utils" "30.2.0" - "@jest/transform" "30.2.0" - "@jest/types" "30.2.0" - babel-preset-current-node-syntax "^1.2.0" - chalk "^4.1.2" - expect "30.2.0" - graceful-fs "^4.2.11" - jest-diff "30.2.0" - jest-matcher-utils "30.2.0" - jest-message-util "30.2.0" - jest-util "30.2.0" - pretty-format "30.2.0" - semver "^7.7.2" - synckit "^0.11.8" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.7.0" - graceful-fs "^4.2.9" - 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" - semver "^7.5.3" - -jest-util@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.2.0.tgz#5142adbcad6f4e53c2776c067a4db3c14f913705" - integrity sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA== - dependencies: - "@jest/types" "30.2.0" - "@types/node" "*" - chalk "^4.1.2" - ci-info "^4.2.0" - graceful-fs "^4.2.11" - picomatch "^4.0.2" - -jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.7.0" - string-length "^4.0.1" - -jest-worker@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.2.0.tgz#fd5c2a36ff6058ec8f74366ec89538cc99539d26" - integrity sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g== - dependencies: - "@types/node" "*" - "@ungap/structured-clone" "^1.3.0" - jest-util "30.2.0" - merge-stream "^2.0.0" - supports-color "^8.1.1" - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@4.1.0, js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -json-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" - integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== - dependencies: - bignumber.js "^9.0.0" - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonc-parser@3.3.1, jsonc-parser@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" - integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonwebtoken@9.0.2, jsonwebtoken@^9.0.0, jsonwebtoken@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" - integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^7.5.4" - -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jwa@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" - integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - -jws@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" - integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== - dependencies: - jwa "^2.0.0" - safe-buffer "^5.0.1" - -keyv@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -libphonenumber-js@^1.11.1: - version "1.12.9" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.12.9.tgz#25626cd825225b326a514a142aaa26717286c4b0" - integrity sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== - -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== - -lodash.isarguments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== - -lodash@4.17.21, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -logform@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.7.0.tgz#cfca97528ef290f2e125a08396805002b2d060d1" - integrity sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ== - dependencies: - "@colors/colors" "1.6.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - -long@^5.0.0: - version "5.3.2" - resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" - integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== - -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -lru-cache@^11.0.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117" - integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -luxon@^3.2.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.6.1.tgz#d283ffc4c0076cb0db7885ec6da1c49ba97e47b0" - integrity sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ== - -luxon@~3.7.0: - version "3.7.2" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.7.2.tgz#d697e48f478553cca187a0f8436aff468e3ba0ba" - integrity sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew== - -magic-string@0.30.17: - version "0.30.17" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" - integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - -magic-string@^0.30.18: - version "0.30.19" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.19.tgz#cebe9f104e565602e5d2098c5f2e79a77cc86da9" - integrity sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.5" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@^1.1.1, make-error@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.4.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@^1.1.2, methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.0, micromatch@^4.0.4, micromatch@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^10.0.3: - version "10.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.3.tgz#cf7a0314a16c4d9ab73a7730a0e8e3c3502d47aa" - integrity sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw== - dependencies: - "@isaacs/brace-expansion" "^5.0.0" - -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.5, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minio@8.0.6: - version "8.0.6" - resolved "https://registry.yarnpkg.com/minio/-/minio-8.0.6.tgz#ec736e3e3ead1f450af5b9b28256c28e52a83dd0" - integrity sha512-sOeh2/b/XprRmEtYsnNRFtOqNRTPDvYtMWh+spWlfsuCV/+IdxNeKVUMKLqI7b5Dr07ZqCPuaRGU/rB9pZYVdQ== - dependencies: - async "^3.2.4" - block-stream2 "^2.1.0" - browser-or-node "^2.1.1" - buffer-crc32 "^1.0.0" - eventemitter3 "^5.0.1" - fast-xml-parser "^4.4.1" - ipaddr.js "^2.0.1" - lodash "^4.17.21" - mime-types "^2.1.35" - query-string "^7.1.3" - stream-json "^1.8.0" - through2 "^4.0.2" - web-encoding "^1.1.5" - xml2js "^0.5.0 || ^0.6.2" - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -mkdirp@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -module-details-from-path@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" - integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.3, ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -msgpackr-extract@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz#e9d87023de39ce714872f9e9504e3c1996d61012" - integrity sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA== - dependencies: - 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" - -msgpackr@^1.11.2: - version "1.11.2" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.11.2.tgz#4463b7f7d68f2e24865c395664973562ad24473d" - integrity sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g== - optionalDependencies: - msgpackr-extract "^3.0.2" - -multer@2.0.2, multer@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/multer/-/multer-2.0.2.tgz#08a8aa8255865388c387aaf041426b0c87bf58dd" - integrity sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw== - 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" - -mute-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" - integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== - -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-abort-controller@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" - integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== - -node-domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-emoji@1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" - integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== - dependencies: - lodash "^4.17.21" - -node-fetch@^2.6.1, node-fetch@^2.6.9: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" - integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== - dependencies: - data-uri-to-buffer "^4.0.0" - fetch-blob "^3.1.4" - formdata-polyfill "^4.0.10" - -node-gyp-build-optional-packages@5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz#522f50c2d53134d7f3a76cd7255de4ab6c96a3a4" - integrity sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw== - dependencies: - detect-libc "^2.0.1" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.27: - version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" - integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -oauth@0.10.x: - version "0.10.2" - resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.2.tgz#fd7139b0ce1a1037bd11fa4e236afc588132418c" - integrity sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q== - -oauth@0.9.x: - version "0.9.15" - resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" - integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA== - -object-assign@^4, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^7.0.3: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -ora@5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -passport-github2@^0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/passport-github2/-/passport-github2-0.1.12.tgz#a72ebff4fa52a35bc2c71122dcf470d1116f772c" - integrity sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw== - dependencies: - passport-oauth2 "1.x.x" - -passport-google-oauth1@1.x.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc" - integrity sha512-qpCEhuflJgYrdg5zZIpAq/K3gTqa1CtHjbubsEsidIdpBPLkEVq6tB1I8kBNcH89RdSiYbnKpCBXAZXX/dtx1Q== - dependencies: - passport-oauth1 "1.x.x" - -passport-google-oauth20@2.x.x, passport-google-oauth20@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef" - integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ== - dependencies: - passport-oauth2 "1.x.x" - -passport-google-oauth@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae" - integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA== - dependencies: - passport-google-oauth1 "1.x.x" - passport-google-oauth20 "2.x.x" - -passport-jwt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d" - integrity sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ== - dependencies: - jsonwebtoken "^9.0.0" - passport-strategy "^1.0.0" - -passport-oauth1@1.x.x: - version "1.3.0" - resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.3.0.tgz#5d57f1415c8e28e46b461a12ec1b492934f7c354" - integrity sha512-8T/nX4gwKTw0PjxP1xfD0QhrydQNakzeOpZ6M5Uqdgz9/a/Ag62RmJxnZQ4LkbdXGrRehQHIAHNAu11rCP46Sw== - dependencies: - oauth "0.9.x" - passport-strategy "1.x.x" - utils-merge "1.x.x" - -passport-oauth2@1.x.x: - version "1.8.0" - resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.8.0.tgz#55725771d160f09bbb191828d5e3d559eee079c8" - integrity sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== - dependencies: - base64url "3.x.x" - oauth "0.10.x" - passport-strategy "1.x.x" - uid2 "0.0.x" - utils-merge "1.x.x" - -passport-strategy@1.x.x, passport-strategy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" - integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== - -passport@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/passport/-/passport-0.7.0.tgz#3688415a59a48cf8068417a8a8092d4492ca3a05" - integrity sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ== - dependencies: - passport-strategy "1.x.x" - pause "0.0.1" - utils-merge "^1.0.1" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-scurry@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" - integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== - dependencies: - lru-cache "^11.0.0" - minipass "^7.1.2" - -path-to-regexp@0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -path-to-regexp@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" - integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pause@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" - integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== - -peek-readable@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-7.0.0.tgz#c6e4e78ec76f7005e5f6b51ffc93fdb91ede6512" - integrity sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ== - -pg-cloudflare@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz#a1f3d226bab2c45ae75ea54d65ec05ac6cfafbef" - integrity sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg== - -pg-connection-string@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.9.1.tgz#bb1fd0011e2eb76ac17360dc8fa183b2d3465238" - integrity sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-pool@^3.10.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.1.tgz#481047c720be2d624792100cac1816f8850d31b2" - integrity sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg== - -pg-protocol@*, pg-protocol@^1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.10.3.tgz#ac9e4778ad3f84d0c5670583bab976ea0a34f69f" - integrity sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ== - -pg-types@2.2.0, pg-types@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@^8.16.3: - version "8.16.3" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.3.tgz#160741d0b44fdf64680e45374b06d632e86c99fd" - integrity sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw== - dependencies: - pg-connection-string "^2.9.1" - pg-pool "^3.10.1" - pg-protocol "^1.10.3" - pg-types "2.2.0" - pgpass "1.0.5" - optionalDependencies: - pg-cloudflare "^1.2.7" - -pgpass@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" - integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== - dependencies: - split2 "^4.1.0" - -picocolors@^1.0.0, picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== - -pirates@^4.0.4, pirates@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" - integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pluralize@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" - integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== - -possible-typed-array-names@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" - integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== - -postcss@^8.5.6: - version "8.5.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" - integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== - dependencies: - nanoid "^3.3.11" - picocolors "^1.1.1" - source-map-js "^1.2.1" - -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" - integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== - -postgres-date@~1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" - integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" - integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== - -pretty-format@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.2.0.tgz#2d44fe6134529aed18506f6d11509d8a62775ebe" - integrity sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA== - dependencies: - "@jest/schemas" "30.0.5" - ansi-styles "^5.2.0" - react-is "^18.3.1" - -pretty-format@^29.0.0, pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -prom-client@^15.1.3: - version "15.1.3" - resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2" - integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g== - dependencies: - "@opentelemetry/api" "^1.4.0" - tdigest "^0.1.1" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -protobufjs@^7.2.4, protobufjs@^7.2.5, protobufjs@^7.3.0: - version "7.5.0" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.0.tgz#a317ad80713e9db43c8e55afa8636a9aa76bb630" - integrity sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -pure-rand@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== - -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - -qs@^6.11.2, qs@^6.7.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== - dependencies: - side-channel "^1.1.0" - -query-string@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" - integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== - dependencies: - decode-uri-component "^0.2.2" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -react-is@^18.0.0, react-is@^18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - -readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" - integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== - -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== - -redis-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== - dependencies: - redis-errors "^1.0.0" - -reflect-metadata@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" - integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -require-in-the-middle@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-8.0.0.tgz#552c2672898c96988d558e38a8c6bc4368955686" - integrity sha512-9s0pnM5tH8G4dSI3pms2GboYOs25LwOGnRMxN/Hx3TYT1K0rh6OjaWf4dI0DAQnMyaEXWoGVnSTPQasqwzTTAA== - dependencies: - debug "^4.3.5" - module-details-from-path "^1.0.3" - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" - integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== - -resolve@^1.20.0: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== - dependencies: - is-core-module "^2.16.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -reusify@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" - integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - -rimraf@^5.0.1: - version "5.0.10" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" - integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== - dependencies: - glob "^10.3.7" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@7.8.1: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - -rxjs@^7.8.2: - version "7.8.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" - integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== - dependencies: - tslib "^2.1.0" - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex-test@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" - integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - is-regex "^1.2.1" - -safe-stable-stringify@^2.3.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" - integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@>=0.6.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" - integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== - -schema-utils@^3.1.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.3.0, schema-utils@^4.3.2, schema-utils@^4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" - integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.4, semver@^7.3.5, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.2: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -server-destroy@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd" - integrity sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ== - -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -sha.js@^2.4.12: - version "2.4.12" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f" - integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w== - dependencies: - inherits "^2.0.4" - safe-buffer "^5.2.1" - to-buffer "^1.2.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.0.6, side-channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1, signal-exit@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -snappy@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/snappy/-/snappy-7.2.2.tgz#dbd9217ae06b651c073856036618c2dc8992ef17" - integrity sha512-iADMq1kY0v3vJmGTuKcFWSXt15qYUz7wFkArOrsSg0IFfI3nJqIJvK2/ZbEIndg7erIJLtAVX2nSOqPz7DcwbA== - optionalDependencies: - "@napi-rs/snappy-android-arm-eabi" "7.2.2" - "@napi-rs/snappy-android-arm64" "7.2.2" - "@napi-rs/snappy-darwin-arm64" "7.2.2" - "@napi-rs/snappy-darwin-x64" "7.2.2" - "@napi-rs/snappy-freebsd-x64" "7.2.2" - "@napi-rs/snappy-linux-arm-gnueabihf" "7.2.2" - "@napi-rs/snappy-linux-arm64-gnu" "7.2.2" - "@napi-rs/snappy-linux-arm64-musl" "7.2.2" - "@napi-rs/snappy-linux-x64-gnu" "7.2.2" - "@napi-rs/snappy-linux-x64-musl" "7.2.2" - "@napi-rs/snappy-win32-arm64-msvc" "7.2.2" - "@napi-rs/snappy-win32-ia32-msvc" "7.2.2" - "@napi-rs/snappy-win32-x64-msvc" "7.2.2" - -source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@^0.5.21, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@0.7.4, source-map@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -split2@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" - integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -sql-highlight@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/sql-highlight/-/sql-highlight-6.0.0.tgz#e62977ed5c7a1644634a1554b8588ee42611a4be" - integrity sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw== - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - -stack-utils@^2.0.3, stack-utils@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -standard-as-callback@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" - integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -stream-chain@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09" - integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== - -stream-json@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.9.1.tgz#e3fec03e984a503718946c170db7d74556c2a187" - integrity sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw== - dependencies: - stream-chain "^2.2.5" - -streamsearch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" - integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strnum@^1.0.5, strnum@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.1.2.tgz#57bca4fbaa6f271081715dbc9ed7cee5493e28e4" - integrity sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA== - -strtok3@^10.2.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-10.2.2.tgz#a4c6d78d15db02c5eb20d92af3eedf81edaf09d2" - integrity sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg== - dependencies: - "@tokenizer/token" "^0.3.0" - peek-readable "^7.0.0" - -superagent@^10.2.3: - version "10.2.3" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-10.2.3.tgz#d1e4986f2caac423c37e38077f9073ccfe73a59b" - integrity sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig== - dependencies: - component-emitter "^1.3.1" - cookiejar "^2.1.4" - debug "^4.3.7" - fast-safe-stringify "^2.1.1" - form-data "^4.0.4" - formidable "^3.5.4" - methods "^1.1.2" - mime "2.6.0" - qs "^6.11.2" - -supertest@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.1.4.tgz#3175e2539f517ca72fdc7992ffff35b94aca7d34" - integrity sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg== - dependencies: - methods "^1.1.2" - superagent "^10.2.3" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0, supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -swagger-ui-dist@5.18.2: - version "5.18.2" - resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz#62013074374d272c04ed3030704b88db5aa8c0b7" - integrity sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw== - dependencies: - "@scarf/scarf" "=1.4.0" - -symbol-observable@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" - integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== - -synckit@^0.11.7, synckit@^0.11.8: - version "0.11.11" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" - integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== - dependencies: - "@pkgr/core" "^0.2.9" - -tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1, tapable@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" - integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== - -tdigest@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" - integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== - dependencies: - bintrees "1.0.2" - -terser-webpack-plugin@^5.3.11: - version "5.3.14" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06" - integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.25" - jest-worker "^27.4.5" - schema-utils "^4.3.0" - serialize-javascript "^6.0.2" - terser "^5.31.1" - -terser@^5.31.1: - version "5.39.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.39.0.tgz#0e82033ed57b3ddf1f96708d123cca717d86ca3a" - integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -through2@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" - integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== - dependencies: - readable-stream "3" - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-buffer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.1.tgz#2ce650cdb262e9112a18e65dc29dcb513c8155e0" - integrity sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ== - dependencies: - isarray "^2.0.5" - safe-buffer "^5.2.1" - typed-array-buffer "^1.0.3" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -token-types@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.0.0.tgz#1ab26be1ef9c434853500c071acfe5c8dd6544a3" - integrity sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA== - dependencies: - "@tokenizer/token" "^0.3.0" - ieee754 "^1.2.1" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -tree-kill@1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - -ts-api-utils@^2.0.1, ts-api-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" - integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== - -ts-jest@^29.4.4: - version "29.4.4" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.4.tgz#fc6fefe28652ed81b8e1381ef8391901d9f81417" - integrity sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw== - dependencies: - bs-logger "^0.2.6" - fast-json-stable-stringify "^2.1.0" - handlebars "^4.7.8" - json5 "^2.2.3" - lodash.memoize "^4.1.2" - make-error "^1.3.6" - semver "^7.7.2" - type-fest "^4.41.0" - yargs-parser "^21.1.1" - -ts-loader@^9.4.3: - version "9.5.2" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.2.tgz#1f3d7f4bb709b487aaa260e8f19b301635d08020" - integrity sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw== - dependencies: - chalk "^4.1.0" - enhanced-resolve "^5.0.0" - micromatch "^4.0.0" - semver "^7.3.4" - source-map "^0.7.4" - -ts-node@^10.9.1: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -tsconfig-paths-webpack-plugin@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz#f7459a8ed1dd4cf66ad787aefc3d37fff3cf07fc" - integrity sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA== - dependencies: - chalk "^4.1.0" - enhanced-resolve "^5.7.0" - tapable "^2.2.1" - tsconfig-paths "^4.1.2" - -tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@2.8.1, tslib@^2.1.0, tslib@^2.6.2, tslib@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^4.41.0: - version "4.41.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" - integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== - -type-is@^1.6.18, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typed-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" - integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-typed-array "^1.1.14" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - -typeorm@^0.3.27: - version "0.3.27" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.27.tgz#f1e8f3cdc820225f168e901e7e1eaca3a3ec6f3c" - integrity sha512-pNV1bn+1n8qEe8tUNsNdD8ejuPcMAg47u2lUGnbsajiNUr3p2Js1XLKQjBMH0yMRMDfdX8T+fIRejFmIwy9x4A== - dependencies: - "@sqltools/formatter" "^1.2.5" - ansis "^3.17.0" - app-root-path "^3.1.0" - buffer "^6.0.3" - dayjs "^1.11.13" - debug "^4.4.0" - dedent "^1.6.0" - dotenv "^16.4.7" - glob "^10.4.5" - sha.js "^2.4.12" - sql-highlight "^6.0.0" - tslib "^2.8.1" - uuid "^11.1.0" - yargs "^17.7.2" - -typescript@5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - -uglify-js@^3.1.4: - version "3.19.3" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" - integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== - -uid2@0.0.x: - version "0.0.4" - resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44" - integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== - -uid@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" - integrity sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g== - dependencies: - "@lukeed/csprng" "^1.0.0" - -uint8array-extras@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.4.0.tgz#e42a678a6dd335ec2d21661333ed42f44ae7cc74" - integrity sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ== - -undici-types@~7.16.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" - integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== - -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a" - integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url-polyfill@^1.1.12: - version "1.1.13" - resolved "https://registry.yarnpkg.com/url-polyfill/-/url-polyfill-1.1.13.tgz#e281edcded747a004b978c70941325b2243bfa97" - integrity sha512-tXzkojrv2SujumYthZ/WjF7jaSfNhSXlYMpE5AYdL2I3D7DCeo+mch8KtW2rUuKjDg+3VXODXHVgipt8yGY/eQ== - -url-template@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" - integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util@^0.12.3: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - -utils-merge@1.0.1, utils-merge@1.x.x, utils-merge@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@9.0.1, uuid@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" - integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== - -uuid@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" - integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== - -uuid@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" - integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w== - -uuid@^8.3.0, uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -v8-to-istanbul@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - -validator@^13.9.0: - version "13.15.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.0.tgz#2dc7ce057e7513a55585109eec29b2c8e8c1aefd" - integrity sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vue-json-pretty@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/vue-json-pretty/-/vue-json-pretty-2.6.0.tgz#30f865ff59a319bd88ff5ae7461682d07b0775e1" - integrity sha512-glz1aBVS35EO8+S9agIl3WOQaW2cJZW192UVKTuGmryx01ZvOVWc4pR3t+5UcyY4jdOfBUgVHjcpRpcnjRhCAg== - -vue@^3.5.21: - version "3.5.21" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.21.tgz#30af9553fd9642870321b7dc547b46c395cb7b91" - integrity sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA== - dependencies: - "@vue/compiler-dom" "3.5.21" - "@vue/compiler-sfc" "3.5.21" - "@vue/runtime-dom" "3.5.21" - "@vue/server-renderer" "3.5.21" - "@vue/shared" "3.5.21" - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -watchpack@^2.4.1, watchpack@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" - integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -web-encoding@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864" - integrity sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA== - dependencies: - util "^0.12.3" - optionalDependencies: - "@zxing/text-encoding" "0.9.0" - -web-streams-polyfill@^3.0.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" - integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -webpack-node-externals@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917" - integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ== - -webpack-sources@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" - integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== - -webpack@5.100.2: - version "5.100.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.100.2.tgz#e2341facf9f7de1d702147c91bcb65b693adf9e8" - integrity sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw== - 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.15.0" - acorn-import-phases "^1.0.3" - browserslist "^4.24.0" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.2" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^4.3.2" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.11" - watchpack "^2.4.1" - webpack-sources "^3.3.3" - -webpack@^5.102.1: - version "5.102.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.102.1.tgz#1003a3024741a96ba99c37431938bf61aad3d988" - integrity sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ== - 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.15.0" - acorn-import-phases "^1.0.3" - browserslist "^4.26.3" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.3" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^4.3.3" - tapable "^2.3.0" - terser-webpack-plugin "^5.3.11" - watchpack "^2.4.4" - webpack-sources "^3.3.3" - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which-typed-array@^1.1.16, which-typed-array@^1.1.2: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - call-bound "^1.0.4" - for-each "^0.3.5" - get-proto "^1.0.1" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -winston-loki@^6.0.7: - version "6.1.3" - resolved "https://registry.yarnpkg.com/winston-loki/-/winston-loki-6.1.3.tgz#f2ed2d20d255007cc485c6153f40514971e665be" - integrity sha512-DjWtJ230xHyYQWr9mZJa93yhwHttn3JEtSYWP8vXZWJOahiQheUhf+88dSIidbGXB3u0oLweV6G1vkL/ouT62Q== - dependencies: - async-exit-hook "2.0.1" - btoa "^1.2.1" - protobufjs "^7.2.4" - url-polyfill "^1.1.12" - winston-transport "^4.3.0" - optionalDependencies: - snappy "^7.2.2" - -winston-transport@^4.3.0, winston-transport@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.9.0.tgz#3bba345de10297654ea6f33519424560003b3bf9" - integrity sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A== - dependencies: - logform "^2.7.0" - readable-stream "^3.6.2" - triple-beam "^1.3.0" - -winston@^3.18.3: - version "3.18.3" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.18.3.tgz#93ac10808c8e1081d723bc8811cd2f445ddfdcd1" - integrity sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww== - dependencies: - "@colors/colors" "^1.6.0" - "@dabh/diagnostics" "^2.0.8" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.7.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.9.0" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -write-file-atomic@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" - integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^4.0.1" - -"xml2js@^0.5.0 || ^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" - integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xml@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" - integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -xtend@^4.0.0, xtend@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yargs-parser@21.1.1, yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.3.1, yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -yoctocolors-cjs@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" - integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== diff --git a/cli/.pre-commit-config.yaml b/cli/.pre-commit-config.yaml deleted file mode 100644 index ae76690a9..000000000 --- a/cli/.pre-commit-config.yaml +++ /dev/null @@ -1,25 +0,0 @@ -files: ^cli/ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: check-docstring-first - - id: check-yaml - - id: end-of-file-fixer - - id: requirements-txt-fixer - - id: trailing-whitespace - - repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - args: [--config, cli/setup.cfg] - - repo: https://github.com/psf/black - rev: 24.4.2 - hooks: - - id: black - language_version: python3.8 - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - args: [--sl] diff --git a/cli/README.md b/cli/README.md index 6ee6a637d..96f048daf 100644 --- a/cli/README.md +++ b/cli/README.md @@ -92,7 +92,7 @@ pytest ``` For the latter you need to have an instance of the backend running locally. See instructions in the root of the repository for this. -On top of that these tests require particular files to be present in the `cli/data/testing` directory. -To see the exact files that are required, see `cli/testing/backend_fixtures.py`. +On top of that these tests require particular files to be present in the `cli/tests/data` directory. +These files are automatically generated by the `cli/tests/generate_test_data.py` script. You also need to make sure to be logged in with the cli with `klein login`. diff --git a/cli/kleinkram/api/client.py b/cli/kleinkram/api/client.py index ca3aa3612..f38cfa2b8 100644 --- a/cli/kleinkram/api/client.py +++ b/cli/kleinkram/api/client.py @@ -39,15 +39,11 @@ QueryParams = Mapping[str, Union[Data, NestedData, ListData]] -def _convert_nested_data_query_params_values( - key: str, values: NestedData -) -> List[Tuple[str, Data]]: +def _convert_nested_data_query_params_values(key: str, values: NestedData) -> List[Tuple[str, Data]]: return [(f"{key}[{k}]", v) for k, v in values.items()] -def _convert_list_data_query_params_values( - key: str, values: ListData -) -> List[Tuple[str, Data]]: +def _convert_list_data_query_params_values(key: str, values: ListData) -> List[Tuple[str, Data]]: return [(key, value) for value in values] @@ -71,9 +67,7 @@ class AuthenticatedClient(httpx.Client): _config: Config _config_lock: Lock - def __init__( - self, config_path: Path = CONFIG_PATH, *args: Any, **kwargs: Any - ) -> None: + def __init__(self, config_path: Path = CONFIG_PATH, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self._config = get_config(path=config_path) @@ -116,9 +110,7 @@ def _refresh_token(self) -> None: self.cookies.set(COOKIE_AUTH_TOKEN, new_access_token) - def _send_request_with_kleinkram_headers( - self, *args: Any, **kwargs: Any - ) -> httpx.Response: + def _send_request_with_kleinkram_headers(self, *args: Any, **kwargs: Any) -> httpx.Response: # add the cli version to the headers headers = kwargs.get("headers") or {} headers.setdefault(CLI_VERSION_HEADER, __version__) @@ -150,9 +142,7 @@ def request( logger.info(f"requesting {method} {full_url}") httpx_params = _convert_query_params_to_httpx_format(params or {}) - response = self._send_request_with_kleinkram_headers( - method, full_url, params=httpx_params, *args, **kwargs - ) + response = self._send_request_with_kleinkram_headers(method, full_url, params=httpx_params, *args, **kwargs) logger.info(f"got response {response}") @@ -170,9 +160,7 @@ def request( raise NotAuthenticated logger.info(f"retrying request {method} {full_url}") - response = self._send_request_with_kleinkram_headers( - method, full_url, params=httpx_params, *args, **kwargs - ) + response = self._send_request_with_kleinkram_headers(method, full_url, params=httpx_params, *args, **kwargs) logger.info(f"got response {response}") return response else: diff --git a/cli/kleinkram/api/deser.py b/cli/kleinkram/api/deser.py index 1085c3dc3..53b22ec39 100644 --- a/cli/kleinkram/api/deser.py +++ b/cli/kleinkram/api/deser.py @@ -13,11 +13,14 @@ import dateutil.parser from kleinkram.errors import ParsingError -from kleinkram.models import File, Run, LogEntry, ActionTemplate +from kleinkram.models import ActionTemplate +from kleinkram.models import File from kleinkram.models import FileState +from kleinkram.models import LogEntry from kleinkram.models import MetadataValue from kleinkram.models import Mission from kleinkram.models import Project +from kleinkram.models import Run __all__ = [ "_parse_project", @@ -127,11 +130,7 @@ def _parse_metadata(tags: List[Dict]) -> Dict[str, MetadataValue]: result = {} try: for tag in tags: - entry = { - tag.get("name"): MetadataValue( - tag.get("valueAsString"), tag.get("datatype") - ) - } + entry = {tag.get("name"): MetadataValue(tag.get("valueAsString"), tag.get("datatype"))} result.update(entry) return result except ValueError as e: @@ -149,9 +148,7 @@ def _parse_project(project_object: ProjectObject) -> Project: description = project_object[ProjectObjectKeys.DESCRIPTION] created_at = _parse_datetime(project_object[ProjectObjectKeys.CREATED_AT]) updated_at = _parse_datetime(project_object[ProjectObjectKeys.UPDATED_AT]) - required_tags = _parse_required_tags( - project_object[ProjectObjectKeys.REQUIRED_TAGS] - ) + required_tags = _parse_required_tags(project_object[ProjectObjectKeys.REQUIRED_TAGS]) except Exception as e: raise ParsingError(f"error parsing project: {project_object}") from e return Project( @@ -229,25 +226,6 @@ def _parse_file(file: FileObject) -> File: return parsed -""" -@dataclass(frozen=True) -class ActionTemplate: - uuid: UUID - access_rights: int - command: str - cpu_cores: int - cpu_memory_gb: int - entrypoint: str - gpu_memory_gb: int - image_name: str - max_runtime_minutes: int - created_at: datetime - name: str - version: str - -""" - - def _parse_action_template(run_object: RunObject) -> ActionTemplate: try: uuid_ = UUID(run_object[TemplateObjectKeys.UUID], version=4) @@ -290,9 +268,7 @@ def _parse_run(run_object: RunObject) -> Run: artifact_url = run_object.get(RunObjectKeys.ARTIFACT_URL) created_at = _parse_datetime(run_object[RunObjectKeys.CREATED_AT]) updated_at = ( - _parse_datetime(run_object[RunObjectKeys.UPDATED_AT]) - if run_object.get(RunObjectKeys.UPDATED_AT) - else None + _parse_datetime(run_object[RunObjectKeys.UPDATED_AT]) if run_object.get(RunObjectKeys.UPDATED_AT) else None ) mission_dict = run_object[RunObjectKeys.MISSION] diff --git a/cli/kleinkram/api/file_transfer.py b/cli/kleinkram/api/file_transfer.py index 3ae3c4bfe..340b8b9d5 100644 --- a/cli/kleinkram/api/file_transfer.py +++ b/cli/kleinkram/api/file_transfer.py @@ -7,7 +7,8 @@ from concurrent.futures import as_completed from enum import Enum from pathlib import Path -from time import monotonic, sleep +from time import monotonic +from time import sleep from typing import Dict from typing import NamedTuple from typing import Optional @@ -34,7 +35,7 @@ logger = logging.getLogger(__name__) UPLOAD_CREDS = "/files/temporaryAccess" -UPLOAD_CONFIRM = "/queue/confirmUpload" +UPLOAD_CONFIRM = "/files/upload/confirm" UPLOAD_CANCEL = "/files/cancelUpload" DOWNLOAD_CHUNK_SIZE = 1024 * 1024 * 16 @@ -56,20 +57,17 @@ class UploadCredentials(NamedTuple): bucket: str -def _confirm_file_upload( - client: AuthenticatedClient, file_id: UUID, file_hash: str -) -> None: +def _confirm_file_upload(client: AuthenticatedClient, file_id: UUID, file_hash: str) -> None: data = { "uuid": str(file_id), "md5": file_hash, + "source": "CLI", } resp = client.post(UPLOAD_CONFIRM, json=data) resp.raise_for_status() -def _cancel_file_upload( - client: AuthenticatedClient, file_id: UUID, mission_id: UUID -) -> None: +def _cancel_file_upload(client: AuthenticatedClient, file_id: UUID, mission_id: UUID) -> None: data = { "uuids": [str(file_id)], "missionUuid": str(mission_id), @@ -96,6 +94,7 @@ def _get_upload_creditials( dct = { "filenames": [internal_filename], "missionUUID": str(mission_id), + "source": "CLI", } resp = client.post(UPLOAD_CREDS, json=dct) resp.raise_for_status() @@ -156,9 +155,7 @@ class UploadState(Enum): CANCELED = 3 -def _get_upload_credentials_with_retry( - client, pbar, filename, mission_id, max_attempts=5 -): +def _get_upload_credentials_with_retry(client, pbar, filename, mission_id, max_attempts=5): """ Retrieves upload credentials with retry logic. @@ -173,9 +170,7 @@ def _get_upload_credentials_with_retry( """ attempt = 0 while attempt < max_attempts: - creds = _get_upload_creditials( - client, internal_filename=filename, mission_id=mission_id - ) + creds = _get_upload_creditials(client, internal_filename=filename, mission_id=mission_id) if creds is not None: return creds @@ -230,9 +225,7 @@ def upload_file( try: _cancel_file_upload(client, creds.file_id, mission_id) except Exception as cancel_e: - logger.error( - f"Failed to cancel upload for {creds.file_id}: {cancel_e}" - ) + logger.error(f"Failed to cancel upload for {creds.file_id}: {cancel_e}") if attempt < 2: # Retry if not the last attempt pbar.update(0) @@ -251,24 +244,19 @@ def _get_file_download(client: AuthenticatedClient, id: UUID) -> str: """\ get the download url for a file by file id """ - resp = client.get( - DOWNLOAD_URL, params={"uuid": str(id), "expires": True, "preview_only": False} - ) + resp = client.get(DOWNLOAD_URL, params={"uuid": str(id), "expires": True, "preview_only": False}) if 400 <= resp.status_code < 500: raise AccessDenied( - f"Failed to download file: {resp.json()['message']}" - f" Status Code: {resp.status_code}", + f"Failed to download file: {resp.json()['message']}" f" Status Code: {resp.status_code}", ) resp.raise_for_status() - return resp.text + return resp.json()["url"] -def _url_download( - url: str, *, path: Path, size: int, overwrite: bool = False, verbose: bool = False -) -> None: +def _url_download(url: str, *, path: Path, size: int, overwrite: bool = False, verbose: bool = False) -> None: if path.exists(): if overwrite: path.unlink() @@ -284,18 +272,11 @@ def _url_download( while downloaded < size: try: headers = {"Range": f"bytes={downloaded}-"} - with httpx.stream( - "GET", url, headers=headers, timeout=S3_READ_TIMEOUT - ) as response: + with httpx.stream("GET", url, headers=headers, timeout=S3_READ_TIMEOUT) as response: # Accept both 206 Partial Content and 200 OK if starting from 0 - if not ( - response.status_code == 206 - or (downloaded == 0 and response.status_code == 200) - ): + if not (response.status_code == 206 or (downloaded == 0 and response.status_code == 200)): response.raise_for_status() - raise RuntimeError( - f"Expected 206 Partial Content, got {response.status_code}" - ) + raise RuntimeError(f"Expected 206 Partial Content, got {response.status_code}") mode = "ab" if downloaded > 0 else "wb" with open(path, mode) as f: @@ -308,9 +289,7 @@ def _url_download( leave=False, disable=not verbose, ) as pbar: - for chunk in response.iter_bytes( - chunk_size=DOWNLOAD_CHUNK_SIZE - ): + for chunk in response.iter_bytes(chunk_size=DOWNLOAD_CHUNK_SIZE): attempt = 0 # reset attempt counter on successful download of non-empty chunk if not chunk: break @@ -322,13 +301,9 @@ def _url_download( logger.info(f"Error: {e}, retrying...") attempt += 1 if attempt > MAX_RETRIES: - raise RuntimeError( - f"Download failed after {MAX_RETRIES} retries due to {e}" - ) from e + raise RuntimeError(f"Download failed after {MAX_RETRIES} retries due to {e}") from e if verbose: - print( - f"{e} on attempt {attempt}/{MAX_RETRIES}, retrying after backoff..." - ) + print(f"{e} on attempt {attempt}/{MAX_RETRIES}, retrying after backoff...") sleep(RETRY_BACKOFF_BASE**attempt) @@ -368,17 +343,13 @@ def download_file( return DownloadState.SKIPPED_OK, 0 elif verbose: - tqdm.write( - styled_string(f"overwriting {path}, hash mismatch", style="yellow") - ) + tqdm.write(styled_string(f"overwriting {path}, hash mismatch", style="yellow")) elif not overwrite and file.size is not None: return DownloadState.SKIPPED_FILE_SIZE_MISMATCH, 0 elif verbose: - tqdm.write( - styled_string(f"overwriting {path}, file size mismatch", style="yellow") - ) + tqdm.write(styled_string(f"overwriting {path}, file size mismatch", style="yellow")) # request a download url download_url = _get_file_download(client, file.id) @@ -408,6 +379,10 @@ def download_file( observed_hash = b64_md5(path) if file.hash is not None and observed_hash != file.hash: + print( + f"HASH MISMATCH: {path} expected={file.hash} observed={observed_hash}", + file=sys.stderr, + ) # Download completed but hash failed return ( DownloadState.DOWNLOADED_INVALID_HASH, @@ -424,9 +399,7 @@ def download_file( } -def _upload_handler( - future: Future[Tuple[UploadState, int]], path: Path, *, verbose: bool = False -) -> int: +def _upload_handler(future: Future[Tuple[UploadState, int]], path: Path, *, verbose: bool = False) -> int: """Returns bytes uploaded successfully.""" state = UploadState.CANCELED # Default to canceled if exception occurs size_bytes = 0 @@ -435,7 +408,7 @@ def _upload_handler( except Exception as e: logger.error(format_traceback(e)) if verbose: - tqdm.write(format_error(f"error uploading", e, verbose=verbose)) + tqdm.write(format_error("error uploading", e, verbose=verbose)) else: print(f"ERROR: {path.absolute()}: {e}", file=sys.stderr) return 0 # Return 0 bytes on error @@ -505,11 +478,7 @@ def _download_handler( elif state not in (DownloadState.DOWNLOADED_OK, DownloadState.SKIPPED_OK): print(f"SKIP/FAIL: {path.absolute()} ({state.name})", file=sys.stderr) - return ( - size_bytes - if state in (DownloadState.DOWNLOADED_OK, DownloadState.SKIPPED_OK) - else 0 - ) + return size_bytes if state in (DownloadState.DOWNLOADED_OK, DownloadState.SKIPPED_OK) else 0 def upload_files( @@ -526,7 +495,7 @@ def upload_files( unit="files", desc="Uploading files", disable=not verbose, - leave=False, + leave=True, ) as pbar: start = monotonic() futures: Dict[Future[Tuple[UploadState, int]], Path] = {} @@ -536,9 +505,7 @@ def upload_files( with ThreadPoolExecutor(max_workers=n_workers) as executor: for name, path in files.items(): if not path.is_file(): - console.print( - f"[yellow]Skipping non-existent file: {path}[/yellow]" - ) + console.print(f"[yellow]Skipping non-existent file: {path}[/yellow]") pbar.update() continue @@ -558,10 +525,7 @@ def upload_files( if future.exception(): failed_files += 1 - if ( - future.exception() is None - and future.result()[0] == UploadState.EXISTS - ): + if future.exception() is None and future.result()[0] == UploadState.EXISTS: skipped_files += 1 path = futures[future] @@ -569,25 +533,25 @@ def upload_files( total_uploaded_bytes += uploaded_bytes pbar.update() - end = monotonic() - elapsed_time = end - start + end = monotonic() + elapsed_time = end - start - avg_speed_bps = total_uploaded_bytes / elapsed_time if elapsed_time > 0 else 0 + avg_speed_bps = total_uploaded_bytes / elapsed_time if elapsed_time > 0 else 0 - if verbose: - console.print(f"Upload took {elapsed_time:.2f} seconds") - console.print(f"Total uploaded: {format_bytes(total_uploaded_bytes)}") - console.print(f"Average speed: {format_bytes(avg_speed_bps, speed=True)}") - - if failed_files > 0: - console.print( - f"\nUploaded {len(files) - failed_files - skipped_files} files, {skipped_files} skipped, {failed_files} uploads failed", - style="red", - ) - else: - console.print( - f"\nUploaded {len(files) - skipped_files} files, {skipped_files} skipped" - ) + if verbose: + console.print() + console.print(f"Upload took {elapsed_time:.2f} seconds") + console.print(f"Total uploaded: {format_bytes(total_uploaded_bytes)}") + console.print(f"Average speed: {format_bytes(avg_speed_bps, speed=True)}") + + if failed_files > 0: + console.print( + f"\nUploaded {len(files) - failed_files - skipped_files} files, " + f"{skipped_files} skipped, {failed_files} uploads failed", + style="red", + ) + else: + console.print(f"\nUploaded {len(files) - skipped_files} files, {skipped_files} skipped") def download_files( @@ -604,7 +568,7 @@ def download_files( unit="files", desc="Downloading files", disable=not verbose, - leave=False, + leave=True, ) as pbar: start = monotonic() @@ -624,18 +588,15 @@ def download_files( total_downloaded_bytes = 0 for future in as_completed(futures): file, path = futures[future] - downloaded_bytes = _download_handler( - future, file, path, verbose=verbose - ) + downloaded_bytes = _download_handler(future, file, path, verbose=verbose) total_downloaded_bytes += downloaded_bytes pbar.update() - end = monotonic() - elapsed_time = end - start - avg_speed_bps = total_downloaded_bytes / elapsed_time if elapsed_time > 0 else 0 + end = monotonic() + elapsed_time = end - start + avg_speed_bps = total_downloaded_bytes / elapsed_time if elapsed_time > 0 else 0 - console.print(f"Download took {elapsed_time:.2f} seconds") - console.print( - f"Total downloaded/verified: {format_bytes(total_downloaded_bytes)}" - ) - console.print(f"Average speed: {format_bytes(avg_speed_bps, speed=True)}") + console.print() + console.print(f"Download took {elapsed_time:.2f} seconds") + console.print(f"Total downloaded/verified: {format_bytes(total_downloaded_bytes)}") + console.print(f"Average speed: {format_bytes(avg_speed_bps, speed=True)}") diff --git a/cli/kleinkram/api/pagination.py b/cli/kleinkram/api/pagination.py index 72bd77c4f..6b3e7c735 100644 --- a/cli/kleinkram/api/pagination.py +++ b/cli/kleinkram/api/pagination.py @@ -1,6 +1,5 @@ from __future__ import annotations -from enum import Enum from typing import Any from typing import Dict from typing import Generator @@ -34,11 +33,18 @@ def paginated_request( params[TAKE] = page_size params[SKIP] = 0 - params[EXACT_MATCH] = str(exact_match).lower() # pass string rather than bool + if exact_match: + params[EXACT_MATCH] = str(exact_match).lower() # pass string rather than bool while True: resp = client.get(endpoint, params=params) - resp.raise_for_status() # TODO: this is fine for now + + # explicitly handle 404 if json contains message + if resp.status_code == 404 and "message" in resp.json(): + raise ValueError(resp.json()["message"]) + + # raise for other errors + resp.raise_for_status() paged_data = resp.json() data_page = cast(List[DataPage], paged_data["data"]) diff --git a/cli/kleinkram/api/query.py b/cli/kleinkram/api/query.py index 470d56867..24d1f080d 100644 --- a/cli/kleinkram/api/query.py +++ b/cli/kleinkram/api/query.py @@ -95,11 +95,7 @@ def mission_query_is_unique(query: MissionQuery) -> bool: return True # a single mission name a unique project spec are specified - if ( - project_query_is_unique(query.project_query) - and len(query.patterns) == 1 - and _pattern_is_unique(query.patterns[0]) - ): + if project_query_is_unique(query.project_query) and len(query.patterns) == 1 and _pattern_is_unique(query.patterns[0]): return True return False @@ -110,10 +106,6 @@ def file_query_is_unique(query: FileQuery) -> bool: return True # a single file name a unique mission spec are specified - if ( - mission_query_is_unique(query.mission_query) - and len(query.patterns) == 1 - and _pattern_is_unique(query.patterns[0]) - ): + if mission_query_is_unique(query.mission_query) and len(query.patterns) == 1 and _pattern_is_unique(query.patterns[0]): return True return False diff --git a/cli/kleinkram/api/routes.py b/cli/kleinkram/api/routes.py index 80622506f..d8f0ca579 100644 --- a/cli/kleinkram/api/routes.py +++ b/cli/kleinkram/api/routes.py @@ -18,23 +18,22 @@ import kleinkram.errors from kleinkram._version import __version__ -from kleinkram.api.client import AuthenticatedClient from kleinkram.api.client import CLI_VERSION_HEADER -from kleinkram.api.deser import ( - FileObject, - _parse_run, - RunObject, - _parse_action_template, -) +from kleinkram.api.client import AuthenticatedClient +from kleinkram.api.deser import FileObject from kleinkram.api.deser import MissionObject from kleinkram.api.deser import ProjectObject +from kleinkram.api.deser import RunObject +from kleinkram.api.deser import _parse_action_template from kleinkram.api.deser import _parse_file from kleinkram.api.deser import _parse_mission from kleinkram.api.deser import _parse_project +from kleinkram.api.deser import _parse_run from kleinkram.api.pagination import paginated_request -from kleinkram.api.query import FileQuery, RunQuery +from kleinkram.api.query import FileQuery from kleinkram.api.query import MissionQuery from kleinkram.api.query import ProjectQuery +from kleinkram.api.query import RunQuery from kleinkram.api.query import file_query_is_unique from kleinkram.api.query import mission_query_is_unique from kleinkram.api.query import project_query_is_unique @@ -50,9 +49,11 @@ from kleinkram.errors import ProjectExists from kleinkram.errors import ProjectNotFound from kleinkram.errors import ProjectValidationError -from kleinkram.models import File, Run, ActionTemplate +from kleinkram.models import ActionTemplate +from kleinkram.models import File from kleinkram.models import Mission from kleinkram.models import Project +from kleinkram.models import Run from kleinkram.utils import is_valid_uuid4 from kleinkram.utils import split_args @@ -150,9 +151,7 @@ def get_files( max_entries: Optional[int] = None, ) -> Generator[File, None, None]: params = _file_query_to_params(file_query) - response_stream = paginated_request( - client, FILE_ENDPOINT, params=params, max_entries=max_entries - ) + response_stream = paginated_request(client, FILE_ENDPOINT, params=params, max_entries=max_entries) yield from map(lambda f: _parse_file(FileObject(f)), response_stream) @@ -162,9 +161,7 @@ def get_missions( max_entries: Optional[int] = None, ) -> Generator[Mission, None, None]: params = _mission_query_to_params(mission_query) - response_stream = paginated_request( - client, MISSION_ENDPOINT, params=params, max_entries=max_entries - ) + response_stream = paginated_request(client, MISSION_ENDPOINT, params=params, max_entries=max_entries) yield from map(lambda m: _parse_mission(MissionObject(m)), response_stream) @@ -185,7 +182,7 @@ def get_projects( yield from map(lambda p: _parse_project(ProjectObject(p)), response_stream) -LIST_ACTIONS_ENDPOINT = "/action/listActions" +LIST_ACTIONS_ENDPOINT = "/actions" def get_runs( @@ -201,7 +198,7 @@ def get_run( client: AuthenticatedClient, run_id: str, ) -> Run: - resp = client.get(f"{ACTION_ENDPOINT}/details", params={"uuid": run_id}) + resp = client.get(f"{ACTION_ENDPOINT}s/{run_id}") if resp.status_code == 404: raise kleinkram.errors.RunNotFound(f"Run not found: {run_id}") resp.raise_for_status() @@ -211,29 +208,23 @@ def get_run( def get_action_templates( client: AuthenticatedClient, ) -> Generator[ActionTemplate, None, None]: - response_stream = paginated_request(client, "/action/listTemplates") + response_stream = paginated_request(client, "/templates") yield from map(lambda p: _parse_action_template(RunObject(p)), response_stream) -def get_project( - client: AuthenticatedClient, query: ProjectQuery, exact_match: bool = False -) -> Project: +def get_project(client: AuthenticatedClient, query: ProjectQuery, exact_match: bool = False) -> Project: """\ get a unique project by specifying a project spec """ if not project_query_is_unique(query): - raise InvalidProjectQuery( - f"Project query does not uniquely determine project: {query}" - ) + raise InvalidProjectQuery(f"Project query does not uniquely determine project: {query}") try: return next(get_projects(client, query, exact_match=exact_match)) except StopIteration: raise ProjectNotFound(f"Project not found: {query}") -def submit_action( - client: AuthenticatedClient, mission_uuid: UUID, template_uuid: UUID -) -> str: +def submit_action(client: AuthenticatedClient, mission_uuid: UUID, template_uuid: UUID) -> str: """ Submits a new action to the API and returns the action UUID. @@ -247,7 +238,7 @@ def submit_action( } typer.echo("Submitting action...") - resp = client.post(f"{ACTION_ENDPOINT}/submit", json=submit_payload) + resp = client.post(f"{ACTION_ENDPOINT}s", json=submit_payload) resp.raise_for_status() # Raises on 4xx/5xx responses response_data = resp.json() @@ -264,9 +255,7 @@ def get_mission(client: AuthenticatedClient, query: MissionQuery) -> Mission: get a unique mission by specifying a mission query """ if not mission_query_is_unique(query): - raise InvalidMissionQuery( - f"Mission query does not uniquely determine mission: {query}" - ) + raise InvalidMissionQuery(f"Mission query does not uniquely determine mission: {query}") try: return next(get_missions(client, query)) except StopIteration: @@ -285,12 +274,8 @@ def get_file(client: AuthenticatedClient, query: FileQuery) -> File: raise kleinkram.errors.FileNotFound(f"File not found: {query}") -def _mission_name_is_available( - client: AuthenticatedClient, mission_name: str, project_id: UUID -) -> bool: - mission_query = MissionQuery( - patterns=[mission_name], project_query=ProjectQuery(ids=[project_id]) - ) +def _mission_name_is_available(client: AuthenticatedClient, mission_name: str, project_id: UUID) -> bool: + mission_query = MissionQuery(patterns=[mission_name], project_query=ProjectQuery(ids=[project_id])) try: _ = get_mission(client, mission_query) except MissionNotFound: @@ -298,26 +283,15 @@ def _mission_name_is_available( return False -def _validate_mission_name( - client: AuthenticatedClient, project_id: UUID, mission_name: str -) -> None: +def _validate_mission_name(client: AuthenticatedClient, project_id: UUID, mission_name: str) -> None: if not _mission_name_is_available(client, mission_name, project_id): - raise MissionExists( - f"Mission with name: `{mission_name}` already exists" - f" in project: {project_id}" - ) + raise MissionExists(f"Mission with name: `{mission_name}` already exists" f" in project: {project_id}") if is_valid_uuid4(mission_name): - raise ValueError( - f"Mission name: `{mission_name}` is a valid UUIDv4, " - "mission names must not be valid UUIDv4's" - ) + raise ValueError(f"Mission name: `{mission_name}` is a valid UUIDv4, " "mission names must not be valid UUIDv4's") if mission_name.endswith(" "): - raise ValueError( - "A mission name cannot end with a whitespace. " - f"The given mission name was '{mission_name}'" - ) + raise ValueError("A mission name cannot end with a whitespace. " f"The given mission name was '{mission_name}'") def _project_name_is_available(client: AuthenticatedClient, project_name: str) -> bool: @@ -329,9 +303,7 @@ def _project_name_is_available(client: AuthenticatedClient, project_name: str) - return False -def _validate_mission_created( - client: AuthenticatedClient, project_id: str, mission_name: str -) -> None: +def _validate_mission_created(client: AuthenticatedClient, project_id: str, mission_name: str) -> None: """ validate that a mission is successfully created """ @@ -394,9 +366,7 @@ def _create_mission( _validate_mission_name(client, project_id, mission_name) if required_tags and not set(required_tags).issubset(metadata.keys()): - raise InvalidMissionMetadata( - f"Mission tags `{required_tags}` are required but missing from metadata: {metadata}" - ) + raise InvalidMissionMetadata(f"Mission tags `{required_tags}` are required but missing from metadata: {metadata}") # we need to translate tag keys to tag type ids tags = _get_tags_map(client, metadata) @@ -414,9 +384,7 @@ def _create_mission( return UUID(resp.json()["uuid"], version=4) -def _create_project( - client: AuthenticatedClient, project_name: str, description: str -) -> UUID: +def _create_project(client: AuthenticatedClient, project_name: str, description: str) -> UUID: _validate_project_name(client, project_name, description) payload = {"name": project_name, "description": description} @@ -426,16 +394,12 @@ def _create_project( return UUID(resp.json()["uuid"], version=4) -def _validate_project_name( - client: AuthenticatedClient, project_name: str, description: str -) -> None: +def _validate_project_name(client: AuthenticatedClient, project_name: str, description: str) -> None: if not _project_name_is_available(client, project_name): raise ProjectExists(f"Project with name: `{project_name}` already exists") if project_name.endswith(" "): - raise ProjectValidationError( - f"Project name must not end with a tailing whitespace: `{project_name}`" - ) + raise ProjectValidationError(f"Project name must not end with a tailing whitespace: `{project_name}`") if not description: raise ProjectValidationError("Project description is required") @@ -449,17 +413,13 @@ def _validate_tag_value(tag_value, tag_datatype) -> None: raise InvalidMissionMetadata(f"Value '{tag_value}' is not a valid NUMBER") elif tag_datatype == "BOOLEAN": if tag_value.lower() not in {"true", "false"}: - raise InvalidMissionMetadata( - f"Value '{tag_value}' is not a valid BOOLEAN (expected 'true' or 'false')" - ) + raise InvalidMissionMetadata(f"Value '{tag_value}' is not a valid BOOLEAN (expected 'true' or 'false')") else: pass # any string is fine # TODO: add check for LOCATION tag datatype -def _get_metadata_type_id_by_name( - client: AuthenticatedClient, tag_name: str -) -> Tuple[Optional[UUID], str]: +def _get_metadata_type_id_by_name(client: AuthenticatedClient, tag_name: str) -> Tuple[Optional[UUID], str]: resp = client.get(TAG_TYPE_BY_NAME, params={"name": tag_name, "take": 1}) if resp.status_code in (403, 404): @@ -474,9 +434,7 @@ def _get_metadata_type_id_by_name( return UUID(data["uuid"], version=4), data["datatype"] -def _get_tags_map( - client: AuthenticatedClient, metadata: Dict[str, str] -) -> Dict[UUID, str]: +def _get_tags_map(client: AuthenticatedClient, metadata: Dict[str, str]) -> Dict[UUID, str]: # TODO: this needs a better endpoint # why are we using metadata type ids as keys??? ret = {} @@ -489,9 +447,7 @@ def _get_tags_map( return ret -def _update_mission( - client: AuthenticatedClient, mission_id: UUID, *, metadata: Dict[str, str] -) -> None: +def _update_mission(client: AuthenticatedClient, mission_id: UUID, *, metadata: Dict[str, str]) -> None: tags_dct = _get_tags_map(client, metadata) payload = { "missionUUID": str(mission_id), @@ -530,12 +486,18 @@ def _get_api_version() -> Tuple[int, int, int]: config = get_config() client = httpx.Client() - resp = client.get( - f"{config.endpoint.api}{GET_STATUS}", headers={CLI_VERSION_HEADER: __version__} - ) - vers = resp.headers["kleinkram-version"].split(".") + resp = client.get(f"{config.endpoint.api}{GET_STATUS}", headers={CLI_VERSION_HEADER: __version__}) + vers_str = resp.headers.get("kleinkram-version") + + if not vers_str: + return (0, 0, 0) - return tuple(map(int, vers)) # type: ignore + vers = vers_str.split(".") + + try: + return tuple(map(int, vers)) # type: ignore + except ValueError: + return (0, 0, 0) def _claim_admin(client: AuthenticatedClient) -> None: @@ -550,9 +512,7 @@ def _claim_admin(client: AuthenticatedClient) -> None: FILE_DELETE_MANY = "/files/deleteMultiple" -def _delete_files( - client: AuthenticatedClient, file_ids: Sequence[UUID], mission_id: UUID -) -> None: +def _delete_files(client: AuthenticatedClient, file_ids: Sequence[UUID], mission_id: UUID) -> None: payload = { "uuids": [str(file_id) for file_id in file_ids], "missionUUID": str(mission_id), diff --git a/cli/kleinkram/auth.py b/cli/kleinkram/auth.py index c33fb4e8e..1e9543460 100644 --- a/cli/kleinkram/auth.py +++ b/cli/kleinkram/auth.py @@ -33,9 +33,7 @@ def _headless_auth(*, url: str) -> None: if auth_token and refresh_token: config = get_config() - config.credentials = Credentials( - auth_token=auth_token, refresh_token=refresh_token - ) + config.credentials = Credentials(auth_token=auth_token, refresh_token=refresh_token) save_config(config) print(f"Authentication complete. Tokens saved to {CONFIG_PATH}.") else: @@ -80,8 +78,88 @@ def _browser_auth(*, url: str) -> None: print(f"Authentication complete. Tokens saved to {CONFIG_PATH}.") +def _direct_oauth_auth(*, endpoint: str, provider: str, user: str) -> None: + """ + Directly authenticate with fake OAuth by programmatically following the OAuth flow. + This bypasses the browser entirely for automated testing. + """ + import requests + + print(f"Authenticating as user {user} with {provider}...") + + try: + # Step 1: Get the authorization code from fake OAuth + # The fake OAuth server will auto-redirect when user parameter is provided + fake_oauth_url = "http://localhost:8004/oauth/authorize" + callback_url = f"{endpoint}/auth/{provider}/callback" + + params = { + "client_id": "some-random-string-it-does-not-matter", + "redirect_uri": callback_url, + "response_type": "code", + "state": "cli-direct", + "user": user, + } + + # Make request to fake OAuth - it will redirect with the auth code + response = requests.get(fake_oauth_url, params=params, allow_redirects=False) + + if response.status_code not in [301, 302, 303, 307, 308]: + raise RuntimeError(f"Expected redirect from OAuth provider, got {response.status_code}") + + # Extract the redirect location + location = response.headers.get("Location") + if not location: + raise RuntimeError("No redirect location from OAuth provider") + + # Parse the callback URL to extract the auth code + parsed = urllib.parse.urlparse(location) + query_params = urllib.parse.parse_qs(parsed.query) + + if "code" not in query_params: + raise RuntimeError(f"No authorization code in redirect: {location}") + + auth_code = query_params["code"][0] + state = query_params.get("state", [None])[0] + + print("Received authorization code, exchanging for tokens...") + + # Step 2: Exchange the code for tokens by calling the backend callback + # Use a session to preserve cookies + session = requests.Session() + callback_params = {"code": auth_code} + if state: + callback_params["state"] = state + + callback_response = session.get(callback_url, params=callback_params, allow_redirects=False) + + # The backend should set cookies and redirect + if callback_response.status_code not in [301, 302, 303, 307, 308]: + raise RuntimeError(f"Expected redirect from callback, got {callback_response.status_code}") + + # Extract tokens from cookies + auth_token = session.cookies.get("authtoken") + refresh_token = session.cookies.get("refreshtoken") + + if not auth_token or not refresh_token: + raise RuntimeError("Failed to get tokens from callback response") + + # Save tokens + config = get_config() + config.credentials = Credentials(auth_token=auth_token, refresh_token=refresh_token) + save_config(config) + print(f"Authentication complete. Tokens saved to {CONFIG_PATH}.") + + except requests.RequestException as e: + raise RuntimeError(f"OAuth flow failed: {e}") + + def login_flow( - *, oAuthProvider: str, key: Optional[str] = None, headless: bool = False + *, + oAuthProvider: str, + key: Optional[str] = None, + headless: bool = False, + user: Optional[str] = None, ) -> None: config = get_config() # use cli key login @@ -90,8 +168,29 @@ def login_flow( save_config(config) return + # If user parameter is provided with fake-oauth, use direct OAuth flow + if user is not None and oAuthProvider == "fake-oauth": + _direct_oauth_auth(endpoint=config.endpoint.api, provider=oAuthProvider, user=user) + return + + # Build OAuth URL with state parameter oauth_url = f"{config.endpoint.api}{OAUTH_SLUG}{oAuthProvider}?state=cli" - if not headless and _has_browser(): + + # Add user parameter if provided (for fake-oauth auto-login) + if user is not None: + oauth_url += f"&user={user}" + + is_port_available = True + try: + server = HTTPServer(("", 8000), OAuthCallbackHandler) + server.server_close() + except OSError: + is_port_available = False + + if not is_port_available: + print("Warning: Port 8000 is not available. Falling back to headless authentication.\n\n") + + if not headless and _has_browser() and is_port_available: _browser_auth(url=oauth_url) else: _headless_auth(url=f"{oauth_url}-no-redirect") diff --git a/cli/kleinkram/cli/_action.py b/cli/kleinkram/cli/_action.py index 32a8f84e1..df7e6d309 100644 --- a/cli/kleinkram/cli/_action.py +++ b/cli/kleinkram/cli/_action.py @@ -9,10 +9,13 @@ import kleinkram.api.routes from kleinkram.api.client import AuthenticatedClient -from kleinkram.api.query import ProjectQuery, MissionQuery +from kleinkram.api.query import MissionQuery +from kleinkram.api.query import ProjectQuery from kleinkram.config import get_shared_state -from kleinkram.printing import print_run_info, print_action_templates_table -from kleinkram.utils import is_valid_uuid4, split_args +from kleinkram.printing import print_action_templates_table +from kleinkram.printing import print_run_info +from kleinkram.utils import is_valid_uuid4 +from kleinkram.utils import split_args HELP = """\ Launch kleinkram actions from predefined templates. @@ -47,15 +50,9 @@ def list_actions() -> None: @action_typer.command(help=RUN_HELP) def run( template_name: str = typer.Argument(..., help="Name or ID of the template to run."), - mission: str = typer.Option( - ..., "--mission", "-m", help="Mission ID or name to run the action on." - ), - project: Optional[str] = typer.Option( - None, "--project", "-p", help="Project ID or name (to scope mission)." - ), - follow: bool = typer.Option( - False, "--follow", "-f", help="Follow the logs of the action run." - ), + mission: str = typer.Option(..., "--mission", "-m", help="Mission ID or name to run the action on."), + project: Optional[str] = typer.Option(None, "--project", "-p", help="Project ID or name (to scope mission)."), + follow: bool = typer.Option(False, "--follow", "-f", help="Follow the logs of the action run."), ) -> None: """ Submits an action to run on a specific mission and optionally follows its logs. @@ -80,7 +77,7 @@ def run( raise typer.Exit(code=1) except kleinkram.errors.InvalidMissionQuery: typer.secho( - f"Error: Mission query is ambiguous. Try specifying a project with -p.", + "Error: Mission query is ambiguous. Try specifying a project with -p.", fg=typer.colors.RED, ) raise typer.Exit(code=1) @@ -94,9 +91,7 @@ def run( template_uuid = UUID(template_name) else: templates = kleinkram.api.routes.get_action_templates(client) - found_template = next( - (t for t in templates if t.name == template_name), None - ) + found_template = next((t for t in templates if t.name == template_name), None) if not found_template: typer.secho( @@ -110,12 +105,8 @@ def run( raise typer.Exit(code=1) try: - action_uuid_str = kleinkram.api.routes.submit_action( - client, mission_uuid, template_uuid - ) - typer.secho( - f"Action submitted. Run ID: {action_uuid_str}", fg=typer.colors.GREEN - ) + action_uuid_str = kleinkram.api.routes.submit_action(client, mission_uuid, template_uuid) + typer.secho(f"Action submitted. Run ID: {action_uuid_str}", fg=typer.colors.GREEN) except httpx.HTTPStatusError as e: typer.secho(f"Error submitting action: {e.response.text}", fg=typer.colors.RED) diff --git a/cli/kleinkram/cli/_download.py b/cli/kleinkram/cli/_download.py index 3fa63029d..69b65264a 100644 --- a/cli/kleinkram/cli/_download.py +++ b/cli/kleinkram/cli/_download.py @@ -22,26 +22,16 @@ """ -download_typer = typer.Typer( - name="download", no_args_is_help=True, invoke_without_command=True, help=HELP -) +download_typer = typer.Typer(name="download", no_args_is_help=True, invoke_without_command=True, help=HELP) @download_typer.callback() def download( - files: Optional[List[str]] = typer.Argument( - None, help="file names, ids or patterns" - ), - projects: Optional[List[str]] = typer.Option( - None, "--project", "-p", help="project names, ids or patterns" - ), - missions: Optional[List[str]] = typer.Option( - None, "--mission", "-m", help="mission names, ids or patterns" - ), + files: Optional[List[str]] = typer.Argument(None, help="file names, ids or patterns"), + projects: Optional[List[str]] = typer.Option(None, "--project", "-p", help="project names, ids or patterns"), + missions: Optional[List[str]] = typer.Option(None, "--mission", "-m", help="mission names, ids or patterns"), dest: str = typer.Option(prompt="destination", help="local path to save the files"), - nested: bool = typer.Option( - False, help="save files in nested directories, project-name/mission-name" - ), + nested: bool = typer.Option(False, help="save files in nested directories, project-name/mission-name"), overwrite: bool = typer.Option( False, help="overwrite files if they already exist and don't match the file size or file hash", @@ -64,9 +54,7 @@ def download( ids=mission_ids, project_query=project_query, ) - file_query = FileQuery( - patterns=file_patterns, ids=file_ids, mission_query=mission_query - ) + file_query = FileQuery(patterns=file_patterns, ids=file_ids, mission_query=mission_query) kleinkram.core.download( client=AuthenticatedClient(), diff --git a/cli/kleinkram/cli/_endpoint.py b/cli/kleinkram/cli/_endpoint.py index c64fdee51..4663ddcc8 100644 --- a/cli/kleinkram/cli/_endpoint.py +++ b/cli/kleinkram/cli/_endpoint.py @@ -50,9 +50,7 @@ def endpoint( console.print(f"Endpoint {name} not found.\n", style="red") console.print(endpoint_table(config)) elif not (name and api and s3): - raise typer.BadParameter( - "to add a new endpoint you must specify the api and s3 endpoints" - ) + raise typer.BadParameter("to add a new endpoint you must specify the api and s3 endpoints") else: new_endpoint = Endpoint(name, api, s3) add_endpoint(config, new_endpoint) diff --git a/cli/kleinkram/cli/_file.py b/cli/kleinkram/cli/_file.py index b85dbbefc..55c8763f5 100644 --- a/cli/kleinkram/cli/_file.py +++ b/cli/kleinkram/cli/_file.py @@ -19,19 +19,13 @@ DELETE_HELP = "delete a file" -file_typer = typer.Typer( - no_args_is_help=True, context_settings={"help_option_names": ["-h", "--help"]} -) +file_typer = typer.Typer(no_args_is_help=True, context_settings={"help_option_names": ["-h", "--help"]}) @file_typer.command(help=INFO_HELP) def info( - project: Optional[str] = typer.Option( - None, "--project", "-p", help="project id or name" - ), - mission: Optional[str] = typer.Option( - None, "--mission", "-m", help="mission id or name" - ), + project: Optional[str] = typer.Option(None, "--project", "-p", help="project id or name"), + mission: Optional[str] = typer.Option(None, "--mission", "-m", help="mission id or name"), file: str = typer.Option(..., "--file", "-f", help="file id or name"), ) -> None: project_ids, project_patterns = split_args([project] if project else []) @@ -58,16 +52,10 @@ def info( @file_typer.command(help=DELETE_HELP) def delete( - project: Optional[str] = typer.Option( - None, "--project", "-p", help="project id or name" - ), - mission: Optional[str] = typer.Option( - None, "--mission", "-m", help="mission id or name" - ), + project: Optional[str] = typer.Option(None, "--project", "-p", help="project id or name"), + mission: Optional[str] = typer.Option(None, "--mission", "-m", help="mission id or name"), file: str = typer.Option(..., "--file", "-f", help="file id or name"), - confirm: bool = typer.Option( - False, "--confirm", "-y", "--yes", help="confirm deletion" - ), + confirm: bool = typer.Option(False, "--confirm", "-y", "--yes", help="confirm deletion"), ) -> None: if not confirm: typer.confirm(f"delete {project} {mission}", abort=True) diff --git a/cli/kleinkram/cli/_file_validator.py b/cli/kleinkram/cli/_file_validator.py index ca8c90dec..deff66a13 100644 --- a/cli/kleinkram/cli/_file_validator.py +++ b/cli/kleinkram/cli/_file_validator.py @@ -1,6 +1,7 @@ from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass +from dataclasses import field from pathlib import Path from typing import List @@ -9,11 +10,9 @@ from kleinkram.config import get_shared_state from kleinkram.errors import DatatypeNotSupported from kleinkram.errors import FileNameNotSupported -from kleinkram.utils import ( - check_filename_is_sanatized, - EXPERIMENTAL_FILE_TYPES, - SUPPORT_FILE_TYPES, -) +from kleinkram.utils import EXPERIMENTAL_FILE_TYPES +from kleinkram.utils import SUPPORT_FILE_TYPES +from kleinkram.utils import check_filename_is_sanatized @dataclass @@ -71,15 +70,11 @@ def _validate_path(self, file: Path) -> None: # 2. Check if the datatype is known at all if not is_standard and not is_experimental: - raise DatatypeNotSupported( - f"Unsupported file type '{file_suffix}' on file {file}" - ) + raise DatatypeNotSupported(f"Unsupported file type '{file_suffix}' on file {file}") # 3. Check if an experimental datatype is allowed if is_experimental and not self.experimental_datatypes: - raise DatatypeNotSupported( - f"Experimental datatype '{file_suffix}' not enabled for file {file}" - ) + raise DatatypeNotSupported(f"Experimental datatype '{file_suffix}' not enabled for file {file}") # 4. Check filename is_bad_name = not check_filename_is_sanatized(file.stem) diff --git a/cli/kleinkram/cli/_list.py b/cli/kleinkram/cli/_list.py index c9f0bfb54..3c375bb3e 100644 --- a/cli/kleinkram/cli/_list.py +++ b/cli/kleinkram/cli/_list.py @@ -23,9 +23,7 @@ """ -list_typer = typer.Typer( - name="list", invoke_without_command=True, help=HELP, no_args_is_help=True -) +list_typer = typer.Typer(name="list", invoke_without_command=True, help=HELP, no_args_is_help=True) @list_typer.command() @@ -34,12 +32,8 @@ def files( None, help="file names, ids or patterns", ), - projects: Optional[List[str]] = typer.Option( - None, "--project", "-p", help="project name or id" - ), - missions: Optional[List[str]] = typer.Option( - None, "--mission", "-m", help="mission name or id" - ), + projects: Optional[List[str]] = typer.Option(None, "--project", "-p", help="project name or id"), + missions: Optional[List[str]] = typer.Option(None, "--mission", "-m", help="mission name or id"), ) -> None: file_ids, file_patterns = split_args(files or []) mission_ids, mission_patterns = split_args(missions or []) @@ -51,9 +45,7 @@ def files( ids=mission_ids, patterns=mission_patterns, ) - file_query = FileQuery( - mission_query=mission_query, patterns=file_patterns, ids=file_ids - ) + file_query = FileQuery(mission_query=mission_query, patterns=file_patterns, ids=file_ids) client = AuthenticatedClient() parsed_files = list(get_files(client, file_query=file_query)) @@ -62,9 +54,7 @@ def files( @list_typer.command() def missions( - projects: Optional[List[str]] = typer.Option( - None, "--project", "-p", help="project name or id" - ), + projects: Optional[List[str]] = typer.Option(None, "--project", "-p", help="project name or id"), missions: Optional[List[str]] = typer.Argument(None, help="mission names"), ) -> None: mission_ids, mission_patterns = split_args(missions or []) diff --git a/cli/kleinkram/cli/_mission.py b/cli/kleinkram/cli/_mission.py index bd80e4ce6..72cc291d5 100644 --- a/cli/kleinkram/cli/_mission.py +++ b/cli/kleinkram/cli/_mission.py @@ -26,18 +26,14 @@ Not implemented yet, open an issue if you want specific functionality """ -mission_typer = typer.Typer( - no_args_is_help=True, context_settings={"help_option_names": ["-h", "--help"]} -) +mission_typer = typer.Typer(no_args_is_help=True, context_settings={"help_option_names": ["-h", "--help"]}) @mission_typer.command(help=CREATE_HELP) def create( project: str = typer.Option(..., "--project", "-p", help="project id or name"), mission_name: str = typer.Option(..., "--mission", "-m", help="mission name"), - metadata: Optional[str] = typer.Option( - None, help="path to metadata file (json or yaml)" - ), + metadata: Optional[str] = typer.Option(None, help="path to metadata file (json or yaml)"), ignore_missing_tags: bool = typer.Option(False, help="ignore mission tags"), ) -> None: project_ids, project_patterns = split_args([project] if project else []) @@ -64,9 +60,7 @@ def create( @mission_typer.command(help=INFO_HELP) def info( - project: Optional[str] = typer.Option( - None, "--project", "-p", help="project id or name" - ), + project: Optional[str] = typer.Option(None, "--project", "-p", help="project id or name"), mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"), ) -> None: mission_ids, mission_patterns = split_args([mission]) @@ -86,9 +80,7 @@ def info( @mission_typer.command(help=UPDATE_HELP) def update( - project: Optional[str] = typer.Option( - None, "--project", "-p", help="project id or name" - ), + project: Optional[str] = typer.Option(None, "--project", "-p", help="project id or name"), mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"), metadata: str = typer.Option(help="path to metadata file (json or yaml)"), ) -> None: @@ -106,9 +98,7 @@ def update( client = AuthenticatedClient() mission_id = get_mission(client, mission_query).id - kleinkram.core.update_mission( - client=client, mission_id=mission_id, metadata=metadata_dct - ) + kleinkram.core.update_mission(client=client, mission_id=mission_id, metadata=metadata_dct) mission_parsed = get_mission(client, mission_query) print_mission_info(mission_parsed, pprint=get_shared_state().verbose) @@ -116,13 +106,9 @@ def update( @mission_typer.command(help=DELETE_HELP) def delete( - project: Optional[str] = typer.Option( - None, "--project", "-p", help="project id or name" - ), + project: Optional[str] = typer.Option(None, "--project", "-p", help="project id or name"), mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"), - confirm: bool = typer.Option( - False, "--confirm", "-y", "--yes", help="confirm deletion" - ), + confirm: bool = typer.Option(False, "--confirm", "-y", "--yes", help="confirm deletion"), ) -> None: project_ids, project_patterns = split_args([project] if project else []) project_query = ProjectQuery(ids=project_ids, patterns=project_patterns) @@ -152,9 +138,7 @@ def delete( @mission_typer.command(help=NOT_IMPLEMENTED_YET) def prune( - project: Optional[str] = typer.Option( - None, "--project", "-p", help="project id or name" - ), + project: Optional[str] = typer.Option(None, "--project", "-p", help="project id or name"), mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"), ) -> None: """\ diff --git a/cli/kleinkram/cli/_project.py b/cli/kleinkram/cli/_project.py index afe489a9a..6276b364c 100644 --- a/cli/kleinkram/cli/_project.py +++ b/cli/kleinkram/cli/_project.py @@ -13,9 +13,7 @@ from kleinkram.printing import print_project_info from kleinkram.utils import split_args -project_typer = typer.Typer( - no_args_is_help=True, context_settings={"help_option_names": ["-h", "--help"]} -) +project_typer = typer.Typer(no_args_is_help=True, context_settings={"help_option_names": ["-h", "--help"]}) NOT_IMPLEMENTED_YET = """\ @@ -31,9 +29,7 @@ @project_typer.command(help=CREATE_HELP) def create( project: str = typer.Option(..., "--project", "-p", help="project name"), - description: str = typer.Option( - ..., "--description", "-d", help="project description" - ), + description: str = typer.Option(..., "--description", "-d", help="project description"), ) -> None: client = AuthenticatedClient() project_id = kleinkram.api.routes._create_project(client, project, description) @@ -43,9 +39,7 @@ def create( @project_typer.command(help=INFO_HELP) -def info( - project: str = typer.Option(..., "--project", "-p", help="project id or name") -) -> None: +def info(project: str = typer.Option(..., "--project", "-p", help="project id or name")) -> None: project_ids, project_patterns = split_args([project]) project_query = ProjectQuery(ids=project_ids, patterns=project_patterns) @@ -57,35 +51,25 @@ def info( @project_typer.command(help=UPDATE_HELP) def update( project: str = typer.Option(..., "--project", "-p", help="project id or name"), - description: Optional[str] = typer.Option( - None, "--description", "-d", help="project description" - ), - new_name: Optional[str] = typer.Option( - None, "--new-name", "-n", "--name", help="new project name" - ), + description: Optional[str] = typer.Option(None, "--description", "-d", help="project description"), + new_name: Optional[str] = typer.Option(None, "--new-name", "-n", "--name", help="new project name"), ) -> None: if description is None and new_name is None: - raise typer.BadParameter( - "nothing to update, provide --description or --new-name" - ) + raise typer.BadParameter("nothing to update, provide --description or --new-name") project_ids, project_patterns = split_args([project]) project_query = ProjectQuery(ids=project_ids, patterns=project_patterns) client = AuthenticatedClient() project_id = get_project(client=client, query=project_query, exact_match=True).id - kleinkram.core.update_project( - client=client, project_id=project_id, description=description, new_name=new_name - ) + kleinkram.core.update_project(client=client, project_id=project_id, description=description, new_name=new_name) project_parsed = get_project(client, ProjectQuery(ids=[project_id])) print_project_info(project_parsed, pprint=get_shared_state().verbose) @project_typer.command(help=DELETE_HELP) -def delete( - project: str = typer.Option(..., "--project", "-p", help="project id or name") -) -> None: +def delete(project: str = typer.Option(..., "--project", "-p", help="project id or name")) -> None: project_ids, project_patterns = split_args([project]) project_query = ProjectQuery(ids=project_ids, patterns=project_patterns) diff --git a/cli/kleinkram/cli/_run.py b/cli/kleinkram/cli/_run.py index 9772a38c0..6bbe3f12c 100644 --- a/cli/kleinkram/cli/_run.py +++ b/cli/kleinkram/cli/_run.py @@ -1,11 +1,12 @@ from __future__ import annotations -import tarfile import os import re import sys +import tarfile import time -from typing import Optional, List +from typing import List +from typing import Optional import requests import typer @@ -14,8 +15,11 @@ from kleinkram.api.client import AuthenticatedClient from kleinkram.api.query import RunQuery from kleinkram.config import get_shared_state -from kleinkram.models import Run, LogEntry -from kleinkram.printing import print_runs_table, print_run_info, print_run_logs +from kleinkram.models import LogEntry +from kleinkram.models import Run +from kleinkram.printing import print_run_info +from kleinkram.printing import print_run_logs +from kleinkram.printing import print_runs_table from kleinkram.utils import split_args HELP = """\ @@ -41,12 +45,8 @@ @run_typer.command(help=LIST_HELP, name="list") def list_runs( - mission: Optional[str] = typer.Option( - None, "--mission", "-m", help="Mission ID or name to filter by." - ), - project: Optional[str] = typer.Option( - None, "--project", "-p", help="Project ID or name to filter by." - ), + mission: Optional[str] = typer.Option(None, "--mission", "-m", help="Mission ID or name to filter by."), + project: Optional[str] = typer.Option(None, "--project", "-p", help="Project ID or name to filter by."), ) -> None: """ List action runs. @@ -68,9 +68,7 @@ def list_runs( @run_typer.command(name="info", help=INFO_HELP) -def get_info( - run_id: str = typer.Argument(..., help="The ID of the run to get information for.") -) -> None: +def get_info(run_id: str = typer.Argument(..., help="The ID of the run to get information for.")) -> None: """ Get detailed information for a single run. """ @@ -82,9 +80,7 @@ def get_info( @run_typer.command(help=LOGS_HELP) def logs( run_id: str = typer.Argument(..., help="The ID of the run to fetch logs for."), - follow: bool = typer.Option( - False, "--follow", "-f", help="Follow the log output in real-time." - ), + follow: bool = typer.Option(False, "--follow", "-f", help="Follow the log output in real-time."), ) -> None: """ Fetch and display logs for a specific run. @@ -129,12 +125,8 @@ def _get_filename_from_cd(cd: str) -> Optional[str]: @run_typer.command(name="download", help=DOWNLOAD_HELP) def download_artifacts( - run_id: str = typer.Argument( - ..., help="The ID of the run to download artifacts for." - ), - output: Optional[str] = typer.Option( - None, "--output", "-o", help="Path or filename to save the artifacts to." - ), + run_id: str = typer.Argument(..., help="The ID of the run to download artifacts for."), + output: Optional[str] = typer.Option(None, "--output", "-o", help="Path or filename to save the artifacts to."), extract: bool = typer.Option( False, "--extract", @@ -180,25 +172,20 @@ def download_artifacts( if output and os.path.isdir(output): filename = os.path.join( output, - _get_filename_from_cd(r.headers.get("content-disposition")) - or f"{run_id}.tar.gz", + _get_filename_from_cd(r.headers.get("content-disposition")) or f"{run_id}.tar.gz", ) total_length = int(r.headers.get("content-length", 0)) # Write to file with Progress Bar with open(filename, "wb") as f: - with typer.progressbar( - length=total_length, label=f"Saving to {filename}" - ) as progress: + with typer.progressbar(length=total_length, label=f"Saving to {filename}") as progress: for chunk in r.iter_content(chunk_size=8192): if chunk: f.write(chunk) progress.update(len(chunk)) - typer.secho( - f"\nSuccessfully downloaded to {filename}", fg=typer.colors.GREEN - ) + typer.secho(f"\nSuccessfully downloaded to {filename}", fg=typer.colors.GREEN) # Extraction Logic if extract: @@ -223,7 +210,7 @@ def download_artifacts( else: tar.extractall(path=extract_path) - typer.secho(f"Successfully extracted.", fg=typer.colors.GREEN) + typer.secho("Successfully extracted.", fg=typer.colors.GREEN) except tarfile.TarError as e: typer.secho(f"Failed to extract archive: {e}", fg=typer.colors.RED) diff --git a/cli/kleinkram/cli/_upload.py b/cli/kleinkram/cli/_upload.py index efed025d2..7ee99bce0 100644 --- a/cli/kleinkram/cli/_upload.py +++ b/cli/kleinkram/cli/_upload.py @@ -11,7 +11,8 @@ from kleinkram.api.client import AuthenticatedClient from kleinkram.api.query import MissionQuery from kleinkram.api.query import ProjectQuery -from kleinkram.cli._file_validator import FileValidator, _report_skipped_files +from kleinkram.cli._file_validator import FileValidator +from kleinkram.cli._file_validator import _report_skipped_files from kleinkram.config import get_shared_state from kleinkram.errors import MissionNotFound from kleinkram.utils import load_metadata @@ -49,29 +50,21 @@ def _handle_no_files_to_upload(original_count: int, uploaded_count: int) -> None if original_count > 0: typer.echo( - typer.style( - "All paths were skipped. No files to upload.", fg=typer.colors.RED - ), + typer.style("All paths were skipped. No files to upload.", fg=typer.colors.RED), err=True, ) else: - typer.echo( - typer.style("No files provided to upload.", fg=typer.colors.RED), err=True - ) + typer.echo(typer.style("No files provided to upload.", fg=typer.colors.RED), err=True) raise typer.Exit(code=1) @upload_typer.callback() def upload( files: List[str] = typer.Argument(help="files to upload"), - project: Optional[str] = typer.Option( - None, "--project", "-p", help="project id or name" - ), + project: Optional[str] = typer.Option(None, "--project", "-p", help="project id or name"), mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"), create: bool = typer.Option(False, help="create mission if it does not exist"), - metadata: Optional[str] = typer.Option( - None, help="path to metadata file (json or yaml)" - ), + metadata: Optional[str] = typer.Option(None, help="path to metadata file (json or yaml)"), fix_filenames: bool = typer.Option( False, help="fix filenames before upload, this does not change the filenames locally", @@ -82,9 +75,7 @@ def upload( "-s", help="skip unsupported file types, badly named files, or directories instead of erroring", ), - experimental_datatypes: bool = typer.Option( - False, help="allow experimental datatypes (yaml, svo2, db3, tum)" - ), + experimental_datatypes: bool = typer.Option(False, help="allow experimental datatypes (yaml, svo2, db3, tum)"), ignore_missing_tags: bool = typer.Option(False, help="ignore mission tags"), ) -> None: original_file_paths = [Path(file) for file in files] @@ -100,9 +91,7 @@ def upload( _report_skipped_files(validator.skipped_files) - _handle_no_files_to_upload( - original_count=len(original_file_paths), uploaded_count=len(files_to_upload) - ) + _handle_no_files_to_upload(original_count=len(original_file_paths), uploaded_count=len(files_to_upload)) try: kleinkram.core.upload( diff --git a/cli/kleinkram/cli/_verify.py b/cli/kleinkram/cli/_verify.py index f989c4249..dfaecf681 100644 --- a/cli/kleinkram/cli/_verify.py +++ b/cli/kleinkram/cli/_verify.py @@ -9,7 +9,8 @@ import kleinkram.core from kleinkram.api.client import AuthenticatedClient -from kleinkram.cli._file_validator import FileValidator, _report_skipped_files +from kleinkram.cli._file_validator import FileValidator +from kleinkram.cli._file_validator import _report_skipped_files from kleinkram.cli._upload import _build_mission_query from kleinkram.config import get_shared_state from kleinkram.printing import print_file_verification_status @@ -23,18 +24,14 @@ verify_typer = typer.Typer(name="verify", invoke_without_command=True, help=HELP) -def _handle_no_files_to_process( - original_count: int, processed_count: int, action: str = "verify" -) -> None: +def _handle_no_files_to_process(original_count: int, processed_count: int, action: str = "verify") -> None: """Checks if any files are left and exits if not.""" if processed_count > 0: return if original_count > 0: typer.echo( - typer.style( - f"All paths were skipped. No files to {action}.", fg=typer.colors.RED - ), + typer.style(f"All paths were skipped. No files to {action}.", fg=typer.colors.RED), err=True, ) else: @@ -48,9 +45,7 @@ def _handle_no_files_to_process( @verify_typer.callback() def verify( files: List[str] = typer.Argument(help="files to verify"), - project: Optional[str] = typer.Option( - None, "--project", "-p", help="project id or name" - ), + project: Optional[str] = typer.Option(None, "--project", "-p", help="project id or name"), mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"), skip: bool = typer.Option( False, @@ -58,9 +53,7 @@ def verify( "-s", help="skip unsupported file types, badly named files, or directories instead of erroring", ), - experimental_datatypes: bool = typer.Option( - False, help="allow experimental datatypes (yaml, svo2, db3, tum)" - ), + experimental_datatypes: bool = typer.Option(False, help="allow experimental datatypes (yaml, svo2, db3, tum)"), skip_hash: bool = typer.Option(None, help="skip hash check"), check_file_hash: bool = typer.Option( True, diff --git a/cli/kleinkram/cli/app.py b/cli/kleinkram/cli/app.py index e4529678c..c467df6fe 100644 --- a/cli/kleinkram/cli/app.py +++ b/cli/kleinkram/cli/app.py @@ -31,8 +31,8 @@ from kleinkram.cli._verify import verify_typer from kleinkram.cli.error_handling import ErrorHandledTyper from kleinkram.cli.error_handling import display_error -from kleinkram.config import Config from kleinkram.config import MAX_TABLE_SIZE +from kleinkram.config import Config from kleinkram.config import check_config_compatibility from kleinkram.config import get_config from kleinkram.config import get_shared_state @@ -141,6 +141,12 @@ def login( ), key: Optional[str] = typer.Option(None, help="CLI key"), headless: bool = typer.Option(False), + user: Optional[str] = typer.Option( + None, + "--user", + "-u", + help="Auto-select user ID for fake-oauth (e.g., 1, 2, 3). Only works with fake-oauth provider.", + ), ) -> None: # logic to resolve the "auto" default @@ -157,7 +163,11 @@ def login( f"Unsupported OAuth provider '{oAuthProvider}'. Supported providers: google, github, fake-oauth." ) - login_flow(oAuthProvider=oAuthProvider, key=key, headless=headless) + # validate that user parameter is only used with fake-oauth + if user is not None and oAuthProvider != "fake-oauth": + raise typer.BadParameter("--user parameter can only be used with fake-oauth provider") + + login_flow(oAuthProvider=oAuthProvider, key=key, headless=headless, user=user) @app.command(rich_help_panel=CommandTypes.AUTH) @@ -205,9 +215,7 @@ def check_version_compatiblity() -> None: def cli( verbose: bool = typer.Option(True, help="Enable verbose mode."), debug: bool = typer.Option(False, help="Enable debug mode."), - version: Optional[bool] = typer.Option( - None, "--version", "-v", callback=_version_callback - ), + version: Optional[bool] = typer.Option(None, "--version", "-v", callback=_version_callback), log_level: Optional[LogLevel] = typer.Option(None, help="Set log level."), max_lines: int = typer.Option( MAX_TABLE_SIZE, @@ -234,9 +242,7 @@ def cli( log_level = LogLevel.WARNING LOG_DIR.mkdir(parents=True, exist_ok=True) - logging.basicConfig( - level=LOG_LEVEL_MAP[log_level], filename=LOG_FILE, format=LOG_FORMAT - ) + logging.basicConfig(level=LOG_LEVEL_MAP[log_level], filename=LOG_FILE, format=LOG_FORMAT) logger.info(f"CLI version: {__version__}") try: @@ -246,7 +252,5 @@ def cli( raise except Exception: err = "failed to check version compatibility" - Console(file=sys.stderr).print( - err, style="yellow" if shared_state.verbose else None - ) + Console(file=sys.stderr).print(err, style="yellow" if shared_state.verbose else None) logger.error(err) diff --git a/cli/kleinkram/cli/error_handling.py b/cli/kleinkram/cli/error_handling.py index 72e55b023..6a48604f4 100644 --- a/cli/kleinkram/cli/error_handling.py +++ b/cli/kleinkram/cli/error_handling.py @@ -23,9 +23,7 @@ class ErrorHandledTyper(typer.Typer): _error_handlers: OrderedDict[Type[Exception], ExceptionHandler] - def error_handler( - self, exc: Type[Exception] - ) -> Callable[[ExceptionHandler], ExceptionHandler]: + def error_handler(self, exc: Type[Exception]) -> Callable[[ExceptionHandler], ExceptionHandler]: def dec(func: ExceptionHandler) -> ExceptionHandler: self._error_handlers[exc] = func return func diff --git a/cli/kleinkram/config.py b/cli/kleinkram/config.py index 0d758c8e0..08688308a 100644 --- a/cli/kleinkram/config.py +++ b/cli/kleinkram/config.py @@ -120,13 +120,9 @@ def _get_default_credentials() -> Dict[str, Credentials]: @dataclass class Config: version: str = __version__ - selected_endpoint: str = field( - default_factory=lambda: _get_default_selected_endpoint().name - ) + selected_endpoint: str = field(default_factory=lambda: _get_default_selected_endpoint().name) endpoints: Dict[str, Endpoint] = field(default_factory=_get_default_endpoints) - endpoint_credentials: Dict[str, Credentials] = field( - default_factory=_get_default_credentials - ) + endpoint_credentials: Dict[str, Credentials] = field(default_factory=_get_default_credentials) @property def endpoint(self) -> Endpoint: @@ -149,9 +145,7 @@ def _config_to_dict(config: Config) -> Dict[str, Any]: return { "version": config.version, "endpoints": {key: value._asdict() for key, value in config.endpoints.items()}, - "endpoint_credentials": { - key: value._asdict() for key, value in config.endpoint_credentials.items() - }, + "endpoint_credentials": {key: value._asdict() for key, value in config.endpoint_credentials.items()}, "selected_endpoint": config.endpoint.name, } @@ -161,16 +155,11 @@ def _config_from_dict(dct: Dict[str, Any]) -> Config: dct["version"], dct["selected_endpoint"], {key: Endpoint(**value) for key, value in dct["endpoints"].items()}, - { - key: Credentials(**value) - for key, value in dct["endpoint_credentials"].items() - }, + {key: Credentials(**value) for key, value in dct["endpoint_credentials"].items()}, ) -def _safe_config_write( - config: Config, path: Path, tmp_dir: Optional[Path] = None -) -> None: +def _safe_config_write(config: Config, path: Path, tmp_dir: Optional[Path] = None) -> None: fd, temp_path = tempfile.mkstemp(dir=tmp_dir) with os.fdopen(fd, "w") as f: json.dump(_config_to_dict(config), f) @@ -248,11 +237,7 @@ def endpoint_table(config: Config) -> Table: table.add_column("S3", style="cyan") for name, endpoint in config.endpoints.items(): - display_name = ( - Text(f"* {name}", style="bold yellow") - if name == config.selected_endpoint - else Text(f" {name}") - ) + display_name = Text(f"* {name}", style="bold yellow") if name == config.selected_endpoint else Text(f" {name}") table.add_row(display_name, endpoint.api, endpoint.s3) return table diff --git a/cli/kleinkram/core.py b/cli/kleinkram/core.py index 2ad967db2..7431c7898 100644 --- a/cli/kleinkram/core.py +++ b/cli/kleinkram/core.py @@ -72,18 +72,14 @@ def download( try: files = list(kleinkram.api.routes.get_files(client, file_query=query)) except httpx.HTTPStatusError: - raise InvalidFileQuery( - f"Files not found. Maybe you forgot to specify mission or project flags: {query}" - ) + raise InvalidFileQuery(f"Files not found. Maybe you forgot to specify mission or project flags: {query}") paths = file_paths_from_files(files, dest=base_dir, allow_nested=nested) if verbose: table = files_to_table(files, title="downloading files...") Console().print(table) - kleinkram.api.file_transfer.download_files( - client, paths, verbose=verbose, overwrite=overwrite - ) + kleinkram.api.file_transfer.download_files(client, paths, verbose=verbose, overwrite=overwrite) def upload( @@ -114,9 +110,7 @@ def upload( if create and mission is None: # check if project exists and get its id at the same time - project = kleinkram.api.routes.get_project( - client, query=query.project_query, exact_match=True - ) + project = kleinkram.api.routes.get_project(client, query=query.project_query, exact_match=True) project_id = project.id project_required_tags = project.required_tags mission_name = check_mission_query_is_creatable(query) @@ -133,9 +127,7 @@ def upload( assert mission is not None, "unreachable" filename_map = get_filename_map(file_paths) - kleinkram.api.file_transfer.upload_files( - client, filename_map, mission.id, verbose=verbose - ) + kleinkram.api.file_transfer.upload_files(client, filename_map, mission.id, verbose=verbose) def verify( @@ -163,12 +155,7 @@ def verify( # check that the mission exists _ = kleinkram.api.routes.get_mission(client, query) - remote_files = { - f.name: f - for f in kleinkram.api.routes.get_files( - client, file_query=FileQuery(mission_query=query) - ) - } + remote_files = {f.name: f for f in kleinkram.api.routes.get_files(client, file_query=FileQuery(mission_query=query))} filename_map = get_filename_map(file_paths) # verify files @@ -223,9 +210,7 @@ def update_file(*, client: AuthenticatedClient, file_id: UUID) -> None: raise NotImplementedError("if you have an idea what this should do, open an issue") -def update_mission( - *, client: AuthenticatedClient, mission_id: UUID, metadata: Dict[str, str] -) -> None: +def update_mission(*, client: AuthenticatedClient, mission_id: UUID, metadata: Dict[str, str]) -> None: # TODO: this funciton will do more than just overwirte the metadata in the future kleinkram.api.routes._update_mission(client, mission_id, metadata=metadata) @@ -238,9 +223,7 @@ def update_project( new_name: Optional[str] = None, ) -> None: # TODO: this function should do more in the future - kleinkram.api.routes._update_project( - client, project_id, description=description, new_name=new_name - ) + kleinkram.api.routes._update_project(client, project_id, description=description, new_name=new_name) def delete_files(*, client: AuthenticatedClient, file_ids: Collection[UUID]) -> None: @@ -257,9 +240,7 @@ def delete_files(*, client: AuthenticatedClient, file_ids: Collection[UUID]) -> found_ids = [f.id for f in files] for file_id in file_ids: if file_id not in found_ids: - raise kleinkram.errors.FileNotFound( - f"file {file_id} not found, did not delete any files" - ) + raise kleinkram.errors.FileNotFound(f"file {file_id} not found, did not delete any files") # to prevent catastrophic mistakes from happening *again* assert set(file_ids) == set([file.id for file in files]), "unreachable" @@ -278,11 +259,7 @@ def delete_files(*, client: AuthenticatedClient, file_ids: Collection[UUID]) -> def delete_mission(*, client: AuthenticatedClient, mission_id: UUID) -> None: mquery = MissionQuery(ids=[mission_id]) mission = kleinkram.api.routes.get_mission(client, mquery) - files = list( - kleinkram.api.routes.get_files( - client, file_query=FileQuery(mission_query=mquery) - ) - ) + files = list(kleinkram.api.routes.get_files(client, file_query=FileQuery(mission_query=mquery))) # delete the files and then the mission kleinkram.api.routes._delete_files(client, [f.id for f in files], mission.id) @@ -291,16 +268,10 @@ def delete_mission(*, client: AuthenticatedClient, mission_id: UUID) -> None: def delete_project(*, client: AuthenticatedClient, project_id: UUID) -> None: pquery = ProjectQuery(ids=[project_id]) - _ = kleinkram.api.routes.get_project( - client, pquery, exact_match=True - ) # check if project exists + _ = kleinkram.api.routes.get_project(client, pquery, exact_match=True) # check if project exists # delete all missions and files - missions = list( - kleinkram.api.routes.get_missions( - client, mission_query=MissionQuery(project_query=pquery) - ) - ) + missions = list(kleinkram.api.routes.get_missions(client, mission_query=MissionQuery(project_query=pquery))) for mission in missions: delete_mission(client=client, mission_id=mission.id) diff --git a/cli/kleinkram/printing.py b/cli/kleinkram/printing.py index b25eb5533..fa09e6f06 100644 --- a/cli/kleinkram/printing.py +++ b/cli/kleinkram/printing.py @@ -2,9 +2,9 @@ import json import sys +import time from dataclasses import asdict from datetime import datetime -import time from pathlib import Path from typing import List from typing import Mapping @@ -24,12 +24,15 @@ from kleinkram.api.client import AuthenticatedClient from kleinkram.config import get_shared_state from kleinkram.core import FileVerificationStatus -from kleinkram.models import File, Run, LogEntry, ActionTemplate +from kleinkram.models import ActionTemplate +from kleinkram.models import File from kleinkram.models import FileState +from kleinkram.models import LogEntry from kleinkram.models import MetadataValue from kleinkram.models import MetadataValueType from kleinkram.models import Mission from kleinkram.models import Project +from kleinkram.models import Run FILE_STATE_COLOR = { FileState.OK: "green", @@ -172,9 +175,7 @@ def missions_to_table(missions: Sequence[Mission]) -> Table: return table -def files_to_table( - files: Sequence[File], *, title: str = "files", delimiters: bool = True -) -> Table: +def files_to_table(files: Sequence[File], *, title: str = "files", delimiters: bool = True) -> Table: table = Table(title=title) table.add_column("project") table.add_column("mission") @@ -238,9 +239,7 @@ def file_info_table(file: File) -> Table: return table -def mission_info_table( - mission: Mission, print_metadata: bool = True -) -> Tuple[Table, ...]: +def mission_info_table(mission: Mission, print_metadata: bool = True) -> Tuple[Table, ...]: table = Table("k", "v", title=f"mission info: {mission.name}", show_header=False) # TODO: add more fields as we store more information in the Mission object @@ -257,9 +256,7 @@ def mission_info_table( return (table,) metadata_table = Table("k", "v", title="mission metadata", show_header=False) - kv_pairs_sorted = sorted( - [(k, v) for k, v in mission.metadata.items()], key=lambda x: x[0] - ) + kv_pairs_sorted = sorted([(k, v) for k, v in mission.metadata.items()], key=lambda x: x[0]) for k, v in kv_pairs_sorted: metadata_table.add_row(k, str(parse_metadata_value(v))) @@ -291,9 +288,7 @@ def file_verification_status_table( return table -def print_file_verification_status( - file_status: Mapping[Path, FileVerificationStatus], *, pprint: bool -) -> None: +def print_file_verification_status(file_status: Mapping[Path, FileVerificationStatus], *, pprint: bool) -> None: """\ prints the file verification status to stdout / stderr either using pprint or as a list for piping @@ -303,9 +298,7 @@ def print_file_verification_status( Console().print(table) else: for path, status in file_status.items(): - stream = ( - sys.stdout if status == FileVerificationStatus.UPLOADED else sys.stderr - ) + stream = sys.stdout if status == FileVerificationStatus.UPLOADED else sys.stderr print(path, file=stream, flush=True) @@ -529,9 +522,7 @@ def action_templates_to_table(templates: Sequence[ActionTemplate]) -> Table: return table -def print_action_templates_table( - templates: Sequence[ActionTemplate], *, pprint: bool -) -> None: +def print_action_templates_table(templates: Sequence[ActionTemplate], *, pprint: bool) -> None: """ Prints the action templates to stdout either using rich or as a simple list of IDs for piping. @@ -576,11 +567,7 @@ def follow_run_logs(client: AuthenticatedClient, run_uuid: str) -> int: printed_log_count = len(run_details.logs) if current_run_state in TERMINAL_STATES: - color = ( - typer.colors.GREEN - if run_details.state.upper() == "DONE" - else typer.colors.RED - ) + color = typer.colors.GREEN if run_details.state.upper() == "DONE" else typer.colors.RED typer.secho( f"\nRun finished with state: {run_details.state} ({run_details.state_cause})", fg=color, diff --git a/cli/kleinkram/utils.py b/cli/kleinkram/utils.py index c305fae66..ed5e262ec 100644 --- a/cli/kleinkram/utils.py +++ b/cli/kleinkram/utils.py @@ -32,9 +32,7 @@ EXPERIMENTAL_FILE_TYPES = [] -def file_paths_from_files( - files: Sequence[File], *, dest: Path, allow_nested: bool = False -) -> Dict[Path, File]: +def file_paths_from_files(files: Sequence[File], *, dest: Path, allow_nested: bool = False) -> Dict[Path, File]: """\ determines the destinations for a sequence of `File` objects, possibly nested by project and mission @@ -44,10 +42,7 @@ def file_paths_from_files( elif not allow_nested: return {dest / file.name: file for file in files} else: - return { - dest / file.project_name / file.mission_name / file.name: file - for file in files - } + return {dest / file.project_name / file.mission_name / file.name: file for file in files} def upper_camel_case_to_words(s: str) -> List[str]: @@ -87,13 +82,10 @@ def check_file_path(file: Path) -> None: if not file.exists(): raise FileNotFoundError(f"{file} does not exist") if file.suffix not in SUPPORT_FILE_TYPES: - raise FileTypeNotSupported( - f"only {', '.join(SUPPORT_FILE_TYPES)} files are supported: {file}" - ) + raise FileTypeNotSupported(f"only {', '.join(SUPPORT_FILE_TYPES)} files are supported: {file}") if not check_filename_is_sanatized(file.stem): raise FileNameNotSupported( - f"only `{''.join(INTERNAL_ALLOWED_CHARS)}` are " - f"allowed in filenames and at most 50chars: {file}" + f"only `{''.join(INTERNAL_ALLOWED_CHARS)}` are " f"allowed in filenames and at most 50chars: {file}" ) @@ -106,9 +98,7 @@ def format_error(msg: str, exc: Exception, *, verbose: bool = False) -> str: def format_traceback(exc: Exception) -> str: - return "".join( - traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__) - ) + return "".join(traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__)) def styled_string(*objects: Any, **kwargs: Any) -> str: @@ -148,9 +138,7 @@ def get_filename(path: Path) -> str: - the 10 hashed chars are deterministic given the original filename """ - stem = "".join( - char if char in INTERNAL_ALLOWED_CHARS else "_" for char in path.stem - ) + stem = "".join(char if char in INTERNAL_ALLOWED_CHARS else "_" for char in path.stem) if len(stem) > 50: hash = md5(path.name.encode()).hexdigest() diff --git a/cli/kleinkram/wrappers.py b/cli/kleinkram/wrappers.py index e11d8c052..0a843acd4 100644 --- a/cli/kleinkram/wrappers.py +++ b/cli/kleinkram/wrappers.py @@ -9,7 +9,8 @@ from __future__ import annotations from pathlib import Path -from typing import Collection, Any +from typing import Any +from typing import Collection from typing import Dict from typing import List from typing import Literal @@ -66,9 +67,7 @@ def _args_to_mission_query( return MissionQuery( ids=[parse_uuid_like(_id) for _id in mission_ids or []], patterns=list(mission_names or []), - project_query=_args_to_project_query( - project_names=project_names, project_ids=project_ids - ), + project_query=_args_to_project_query(project_names=project_names, project_ids=project_ids), ) @@ -85,9 +84,7 @@ def _verify_string_sequence(arg_name: str, arg_value: Optional[Sequence[Any]]) - if not isinstance(arg_value, Sequence): raise TypeError(f"{arg_name} must be a Sequence, None, or empty array.") if isinstance(arg_value, str): - raise TypeError( - f"{arg_name} cannot be a string, but a sequence of strings." - ) + raise TypeError(f"{arg_name} cannot be a string, but a sequence of strings.") for item in arg_value: if not isinstance(item, str): raise TypeError(f"{arg_name} must contain strings only.") @@ -358,15 +355,11 @@ def create_mission( def create_project(project_name: str, description: str) -> None: - kleinkram.api.routes._create_project( - AuthenticatedClient(), project_name, description - ) + kleinkram.api.routes._create_project(AuthenticatedClient(), project_name, description) def update_file(file_id: IdLike) -> None: - kleinkram.core.update_file( - client=AuthenticatedClient(), file_id=parse_uuid_like(file_id) - ) + kleinkram.core.update_file(client=AuthenticatedClient(), file_id=parse_uuid_like(file_id)) def update_mission(mission_id: IdLike, metadata: Dict[str, str]) -> None: @@ -399,48 +392,34 @@ def delete_file(file_id: IdLike) -> None: """\ delete a single file by id """ - file = kleinkram.api.routes.get_file( - AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)]) - ) - kleinkram.api.routes._delete_files( - AuthenticatedClient(), file_ids=[file.id], mission_id=file.mission_id - ) + file = kleinkram.api.routes.get_file(AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)])) + kleinkram.api.routes._delete_files(AuthenticatedClient(), file_ids=[file.id], mission_id=file.mission_id) def delete_mission(mission_id: IdLike) -> None: - kleinkram.core.delete_mission( - client=AuthenticatedClient(), mission_id=parse_uuid_like(mission_id) - ) + kleinkram.core.delete_mission(client=AuthenticatedClient(), mission_id=parse_uuid_like(mission_id)) def delete_project(project_id: IdLike) -> None: - kleinkram.core.delete_project( - client=AuthenticatedClient(), project_id=parse_uuid_like(project_id) - ) + kleinkram.core.delete_project(client=AuthenticatedClient(), project_id=parse_uuid_like(project_id)) def get_file(file_id: IdLike) -> File: """\ get a file by its id """ - return kleinkram.api.routes.get_file( - AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)]) - ) + return kleinkram.api.routes.get_file(AuthenticatedClient(), FileQuery(ids=[parse_uuid_like(file_id)])) def get_mission(mission_id: IdLike) -> Mission: """\ get a mission by its id """ - return kleinkram.api.routes.get_mission( - AuthenticatedClient(), MissionQuery(ids=[parse_uuid_like(mission_id)]) - ) + return kleinkram.api.routes.get_mission(AuthenticatedClient(), MissionQuery(ids=[parse_uuid_like(mission_id)])) def get_project(project_id: IdLike) -> Project: """\ get a project by its id """ - return kleinkram.api.routes.get_project( - AuthenticatedClient(), ProjectQuery(ids=[parse_uuid_like(project_id)]) - ) + return kleinkram.api.routes.get_project(AuthenticatedClient(), ProjectQuery(ids=[parse_uuid_like(project_id)])) diff --git a/cli/pyproject.toml b/cli/pyproject.toml index 26c90eb47..dcff8a778 100644 --- a/cli/pyproject.toml +++ b/cli/pyproject.toml @@ -1,2 +1,2 @@ -[too.black] +[tool.black] line-length = 127 diff --git a/cli/requirements-dev.txt b/cli/requirements-dev.txt index 081de85b2..15bb3cb6f 100644 --- a/cli/requirements-dev.txt +++ b/cli/requirements-dev.txt @@ -3,4 +3,5 @@ flake8 mypy pre-commit pytest +rosbags types-boto3 diff --git a/cli/requirements.txt b/cli/requirements.txt index c17410ba1..ac65c1c93 100644 --- a/cli/requirements.txt +++ b/cli/requirements.txt @@ -1,10 +1,10 @@ boto3 botocore +click httpx python-dateutil pyyaml +requests rich tqdm typer -click -requests diff --git a/cli/scripts/manual_test.py b/cli/scripts/manual_test.py index 70adff657..2c72c42d7 100644 --- a/cli/scripts/manual_test.py +++ b/cli/scripts/manual_test.py @@ -15,13 +15,9 @@ def upload_files(mission_name: str) -> None: console = Console() - console.print( - f"testing upload of files for mission {mission_name}...", style="bold green" - ) + console.print(f"testing upload of files for mission {mission_name}...", style="bold green") - cmd = ( - f"{CLI} upload -p {TESTING_PROJECT} -m {mission_name} --create {TESTING_FILES}" - ) + cmd = f"{CLI} upload -p {TESTING_PROJECT} -m {mission_name} --create {TESTING_FILES}" console.print(f"running command: {cmd}", style="bold green") os.system(cmd) diff --git a/cli/setup.cfg b/cli/setup.cfg index bf886eed3..c1051814c 100644 --- a/cli/setup.cfg +++ b/cli/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = kleinkram -version = 0.55.2 +version = 0.56.0 description = give me your bags long_description = file: README.md long_description_content_type = text/markdown diff --git a/cli/testing/__init__.py b/cli/testing/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cli/tests/api/test_client.py b/cli/tests/api/test_client.py index 9b2e6f0c3..3aeb0bd13 100644 --- a/cli/tests/api/test_client.py +++ b/cli/tests/api/test_client.py @@ -29,9 +29,7 @@ def config_path(): @pytest.fixture def empty_config(config_path): test_creds = Credentials(api_key="test") - config = Config( - endpoint_credentials={"local": test_creds}, selected_endpoint="local" - ) + config = Config(endpoint_credentials={"local": test_creds}, selected_endpoint="local") save_config(config, config_path) return config_path @@ -43,17 +41,13 @@ def mock_transport(request: httpx.Request) -> httpx.Response: def test_client_sending_kleinkram_version_header(empty_config): - with AuthenticatedClient( - config_path=empty_config, transport=httpx.MockTransport(mock_transport) - ) as client: + with AuthenticatedClient(config_path=empty_config, transport=httpx.MockTransport(mock_transport)) as client: resp = client.get("/example") assert resp.status_code == 200 def test_client_sending_kleinkram_version_header_with_custom_headers(empty_config): - with AuthenticatedClient( - config_path=empty_config, transport=httpx.MockTransport(mock_transport) - ) as client: + with AuthenticatedClient(config_path=empty_config, transport=httpx.MockTransport(mock_transport)) as client: resp = client.get("/example", headers={"foo": "bar"}) assert resp.status_code == 200 @@ -63,9 +57,7 @@ def return_426_response(request: httpx.Request) -> httpx.Response: _ = request return httpx.Response(426) - with AuthenticatedClient( - config_path=empty_config, transport=httpx.MockTransport(return_426_response) - ) as client: + with AuthenticatedClient(config_path=empty_config, transport=httpx.MockTransport(return_426_response)) as client: with pytest.raises(kleinkram.errors.UpdateCLIVersion): client.get("/example") @@ -98,15 +90,11 @@ def test_convert_list_data_query_params_values(): key = "foo" values = ["foo1", "foo2"] expected = [("foo", "foo1"), ("foo", "foo2")] - assert sorted(_convert_list_data_query_params_values(key, values)) == sorted( - expected - ) + assert sorted(_convert_list_data_query_params_values(key, values)) == sorted(expected) def test_convert_nested_data_query_params_values(): key = "foo" values = {"k1": "v1", "k2": "v2"} expected = [("foo[k1]", "v1"), ("foo[k2]", "v2")] - assert sorted(_convert_nested_data_query_params_values(key, values)) == sorted( - expected - ) + assert sorted(_convert_nested_data_query_params_values(key, values)) == sorted(expected) diff --git a/cli/testing/backend_fixtures.py b/cli/tests/backend_fixtures.py similarity index 62% rename from cli/testing/backend_fixtures.py rename to cli/tests/backend_fixtures.py index 4197d4ecd..9ff4e852c 100644 --- a/cli/testing/backend_fixtures.py +++ b/cli/tests/backend_fixtures.py @@ -1,5 +1,7 @@ from __future__ import annotations +import subprocess +import sys import time from pathlib import Path from secrets import token_hex @@ -14,7 +16,7 @@ from kleinkram import upload # we expect the mission files to be in this folder that is not commited to the repo -DATA_PATH = Path(__file__).parent.parent / "data" / "testing" +DATA_PATH = Path(__file__).parent / "data" DATA_FILES = [ DATA_PATH / "10_KB.bag", DATA_PATH / "50_KB.bag", @@ -26,7 +28,7 @@ PROJECT_DESCRIPTION = "This is a test project" -WAIT_BEOFORE_DELETION = 5 +WAIT_BEFORE_DELETION = 5 @pytest.fixture(scope="session") @@ -38,7 +40,7 @@ def project(): yield project - time.sleep(WAIT_BEOFORE_DELETION) + time.sleep(WAIT_BEFORE_DELETION) delete_project(project.id) @@ -63,3 +65,25 @@ def empty_mission(project): mission = list_missions(project_ids=[project.id], mission_names=[mission_name])[0] yield mission + + +@pytest.fixture(scope="session", autouse=True) +def auto_login(): + """ + Automatically logs in using the CLI before running any tests. + """ + try: + # Run: python3 -m kleinkram login --user 1 + result = subprocess.run( + [sys.executable, "-m", "kleinkram", "login", "--user", "1"], + cwd=str(Path(__file__).parent.parent), # Run from cli root + capture_output=True, + text=True, + ) + if result.returncode != 0: + pytest.fail( + f"Failed to auto-login. Return code: {result.returncode}\nStdout: {result.stdout}\nStderr: {result.stderr}" + ) + + except Exception as e: + pytest.fail(f"Failed to auto-login: {e}") diff --git a/cli/tests/conftest.py b/cli/tests/conftest.py index ffa474faf..4bd62c7e2 100644 --- a/cli/tests/conftest.py +++ b/cli/tests/conftest.py @@ -3,5 +3,5 @@ import pytest pytest_plugins = [ - "testing.backend_fixtures", + "tests.backend_fixtures", ] diff --git a/cli/tests/generate_test_data.py b/cli/tests/generate_test_data.py new file mode 100644 index 000000000..5dc84479d --- /dev/null +++ b/cli/tests/generate_test_data.py @@ -0,0 +1,314 @@ +from __future__ import annotations + +import os +import struct +import time + +from rosbags.rosbag1 import Writer + + +def serialize_string(data): + """Serialize a string for ROS1 (std_msgs/String).""" + encoded = data.encode("utf-8") + return struct.pack(" 1024 * 1024: + payload_size = 1024 * 1024 + + if target_size < 2000: # Very small files + payload_size = 100 + + payload = "x" * payload_size + serialized_msg = serialize_string(payload) + + # Calculate number of messages needed + # Approximate overhead per message in bag file is ~30-50 bytes (record header) + connection ref + # We'll assume overhead is small compared to payload for large files, but significant for small ones. + # We'll just write until we think we are close. + + msg_size = len(serialized_msg) + 50 # rough estimate including record headers + num_msgs = int(target_size / msg_size) + 1 + if num_msgs < 1: + num_msgs = 1 + + print(f"Generating {filename} (~{target_size} bytes) with {num_msgs} messages of payload {payload_size}...") + + if os.path.exists(filename): + os.remove(filename) + + with Writer(filename) as writer: + # Add a connection + # msg_def for std_msgs/String is just "string data" + conn = writer.add_connection( + topic="/test_topic", + msgtype="std_msgs/msg/String", + msgdef="string data", + md5sum="992ce8a1687cec8c8bd883ec73ca41d1", + ) + timestamp = 1000 + for i in range(num_msgs): + writer.write(conn, timestamp + i, serialized_msg) + + +def generate_frontend_bag(filename): + print(f"Generating frontend test bag: {filename}") + if os.path.exists(filename): + os.remove(filename) + + with Writer(filename) as writer: + # 1. rosgraph_msgs/Log + conn_log = writer.add_connection( + topic="/rosout", + msgtype="rosgraph_msgs/msg/Log", + msgdef="Header header\nbyte level\nstring name\nstring msg\nstring file\nstring function\nuint32 " + "line\nstring[] topics\n================================================================================\n" + "MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id", + md5sum="acffd30cd6b6de30f120938c17c593fb", + ) + # 2. sensor_msgs/Temperature + conn_temp = writer.add_connection( + topic="/sensors/temperature", + msgtype="sensor_msgs/msg/Temperature", + msgdef="Header header\nfloat64 temperature\nfloat64 variance" + "\n================================================================================\n" + "MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id", + md5sum="ff71b307acdbe7c871a5a6d7edce2f6e", + ) + # 3. sensor_msgs/TimeReference + conn_time = writer.add_connection( + topic="/time_ref", + msgtype="sensor_msgs/msg/TimeReference", + msgdef="Header header\ntime time_ref\nstring source\n" + "================================================================================\n" + "MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id", + md5sum="fded64a0265108ba86c3d38fb11c0c16", + ) + # 4. geometry_msgs/TwistStamped + conn_twist = writer.add_connection( + topic="/cmd_vel", + msgtype="geometry_msgs/msg/TwistStamped", + msgdef="Header header\ngeometry_msgs/Twist twist\n" + "================================================================================\n" + "MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id\n" + "================================================================================\n" + "MSG: geometry_msgs/Twist\nVector3 linear\nVector3 angular\n" + "================================================================================\n" + "MSG: geometry_msgs/Vector3\nfloat64 x\nfloat64 y\nfloat64 z", + md5sum="98d34b0043a2093cf9d9345ab6eef12e", + ) + # 5. tf2_msgs/TFMessage + conn_tf = writer.add_connection( + topic="/tf", + msgtype="tf2_msgs/msg/TFMessage", + msgdef="geometry_msgs/TransformStamped[] transforms\n" + "================================================================================\n" + "MSG: geometry_msgs/TransformStamped\nHeader header\nstring child_frame_id\n" + "geometry_msgs/Transform transform\n" + "================================================================================\n" + "MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id\n" + "================================================================================\n" + "MSG: geometry_msgs/Transform\ngeometry_msgs/Vector3 translation\n" + "geometry_msgs/Quaternion rotation\n" + "================================================================================\n" + "MSG: geometry_msgs/Vector3\nfloat64 x\nfloat64 y\nfloat64 z\n" + "================================================================================\n" + "MSG: geometry_msgs/Quaternion\nfloat64 x\nfloat64 y\nfloat64 z\nfloat64 w", + md5sum="94810edda583a504dfda3829e70d7eec", + ) + + # Write messages + for i in range(100): + timestamp = 1000 + i * 100000000 # 100ms steps + secs = int(timestamp / 1000000000) + nsecs = timestamp % 1000000000 + + # Log + writer.write( + conn_log, + timestamp, + serialize_log( + i, + secs, + nsecs, + "", + 2, + "test_node", + f"Log message {i}", + "test.cpp", + "main", + i, + ["/rosout"], + ), + ) + + # Temperature (sine wave) + import math + + temp = 20.0 + 5.0 * math.sin(i * 0.1) + writer.write( + conn_temp, + timestamp, + serialize_temperature(i, secs, nsecs, "sensor_frame", temp, 0.1), + ) + + # TimeReference + writer.write( + conn_time, + timestamp, + serialize_time_reference(i, secs, nsecs, "time_frame", secs, nsecs, "GPS"), + ) + + # TwistStamped (circle) + writer.write( + conn_twist, + timestamp, + serialize_twist_stamped(i, secs, nsecs, "base_link", [1.0, 0.0, 0.0], [0.0, 0.0, 0.5]), + ) + + # TF + writer.write( + conn_tf, + timestamp, + serialize_tf_message( + [ + { + "seq": i, + "secs": secs, + "nsecs": nsecs, + "frame_id": "map", + "child_frame_id": "base_link", + "tx": i * 0.1, + "ty": 0.0, + "tz": 0.0, + "rx": 0.0, + "ry": 0.0, + "rz": 0.0, + "rw": 1.0, + } + ] + ), + ) + + +def main(): + data_dir = os.path.join(os.path.dirname(__file__), "data") + os.makedirs(data_dir, exist_ok=True) + + files = { + "10_KB.bag": 10 * 1024, + "50_KB.bag": 50 * 1024, + "1_MB.bag": 1 * 1024 * 1024, + "17_MB.bag": 17 * 1024 * 1024, + "125_MB.bag": 125 * 1024 * 1024, + } + + for filename, size in files.items(): + filepath = os.path.join(data_dir, filename) + generate_bag(filepath, size) + + # Generate backend fixtures + backend_fixtures_dir = os.path.join(os.path.dirname(__file__), "../../backend/tests/fixtures") + os.makedirs(backend_fixtures_dir, exist_ok=True) + generate_bag(os.path.join(backend_fixtures_dir, "test.bag"), 10 * 1024) + generate_bag(os.path.join(backend_fixtures_dir, "to_delete.bag"), 10 * 1024) + generate_bag(os.path.join(backend_fixtures_dir, "file1.bag"), 10 * 1024) + generate_bag(os.path.join(backend_fixtures_dir, "file2.bag"), 10 * 1024) + generate_bag(os.path.join(backend_fixtures_dir, "move_me.bag"), 10 * 1024) + generate_bag(os.path.join(backend_fixtures_dir, "move_me.bag"), 10 * 1024) + generate_bag(os.path.join(backend_fixtures_dir, "state_test.bag"), 10 * 1024) + generate_frontend_bag(os.path.join(data_dir, "frontend_test.bag")) + + # Generate dummy MCAP and YAML + with open(os.path.join(data_dir, "test.yaml"), "w") as f: + f.write("test: true\nvalue: 123\n") + + with open(os.path.join(data_dir, "test.mcap"), "wb") as f: + f.write(b"\x89MCAP\x30\r\n") # Minimal magic bytes + + print("Done.") + + +if __name__ == "__main__": + main() diff --git a/cli/tests/test_config.py b/cli/tests/test_config.py index f0bd84eec..d6c7e8a2f 100644 --- a/cli/tests/test_config.py +++ b/cli/tests/test_config.py @@ -72,9 +72,7 @@ def test_load_config_default(config_path): assert config.selected_endpoint == get_env().value -def test_load_default_config_with_env_var_api_key_specified( - config_path, set_api_key_env -): +def test_load_default_config_with_env_var_api_key_specified(config_path, set_api_key_env): assert set_api_key_env is None config = _load_config(path=config_path) @@ -87,9 +85,7 @@ def test_load_default_config_with_env_var_api_key_specified( assert not config_path.exists() -def test_load_default_config_with_env_var_endpoints_specified( - config_path, set_endpoint_env -): +def test_load_default_config_with_env_var_endpoints_specified(config_path, set_endpoint_env): assert set_endpoint_env is None config = _load_config(path=config_path) diff --git a/cli/tests/test_core.py b/cli/tests/test_core.py index 57907727b..f05f54c11 100644 --- a/cli/tests/test_core.py +++ b/cli/tests/test_core.py @@ -17,43 +17,33 @@ from kleinkram.api.query import ProjectQuery from kleinkram.errors import MissionNotFound from kleinkram.models import FileVerificationStatus -from testing.backend_fixtures import DATA_FILES +from tests.backend_fixtures import DATA_FILES @pytest.mark.slow def test_upload_create(project): mission_name = token_hex(8) - mission_query = MissionQuery( - patterns=[mission_name], project_query=ProjectQuery(ids=[project.id]) - ) + mission_query = MissionQuery(patterns=[mission_name], project_query=ProjectQuery(ids=[project.id])) client = AuthenticatedClient() - kleinkram.core.upload( - client=client, query=mission_query, file_paths=DATA_FILES, create=True - ) + kleinkram.core.upload(client=client, query=mission_query, file_paths=DATA_FILES, create=True) mission = list_missions(mission_names=[mission_name])[0] assert mission.project_id == project.id assert mission.name == mission_name files = list_files(mission_ids=[mission.id]) - assert set([file.name for file in files if file.name.endswith(".bag")]) == set( - [file.name for file in DATA_FILES] - ) + assert set([file.name for file in files if file.name.endswith(".bag")]) == set([file.name for file in DATA_FILES]) @pytest.mark.slow def test_upload_no_create(project): mission_name = token_hex(8) - mission_query = MissionQuery( - patterns=[mission_name], project_query=ProjectQuery(ids=[project.id]) - ) + mission_query = MissionQuery(patterns=[mission_name], project_query=ProjectQuery(ids=[project.id])) client = AuthenticatedClient() with pytest.raises(MissionNotFound): - kleinkram.core.upload( - client=client, query=mission_query, file_paths=DATA_FILES, create=False - ) + kleinkram.core.upload(client=client, query=mission_query, file_paths=DATA_FILES, create=False) @pytest.mark.slow @@ -64,9 +54,7 @@ def test_upload_to_existing_mission(empty_mission): kleinkram.core.upload(client=client, query=mission_query, file_paths=DATA_FILES) files = list_files(mission_ids=[empty_mission.id]) - assert set([file.name for file in files if file.name.endswith(".bag")]) == set( - [file.name for file in DATA_FILES] - ) + assert set([file.name for file in files if file.name.endswith(".bag")]) == set([file.name for file in DATA_FILES]) @pytest.mark.slow @@ -84,9 +72,7 @@ def test_delete_working_as_expected_when_passing_empty_list(mission): # we need to filter by *.bag to not get flakyness due to conversion n_files = len(list_files(mission_ids=[mission.id], file_names=["*.bag"])) kleinkram.core.delete_files(client=client, file_ids=[]) - n_files_after_delete = len( - list_files(mission_ids=[mission.id], file_names=["*.bag"]) - ) + n_files_after_delete = len(list_files(mission_ids=[mission.id], file_names=["*.bag"])) assert n_files == n_files_after_delete @@ -132,9 +118,7 @@ def test_create_update_delete_project(): assert project.description == "test" new_name = token_hex(8) - kleinkram.core.update_project( - client=client, project_id=project.id, new_name=new_name, description="new desc" - ) + kleinkram.core.update_project(client=client, project_id=project.id, new_name=new_name, description="new desc") project = list_projects(project_ids=[project.id])[0] assert project.name == new_name @@ -162,13 +146,9 @@ def test_verify(mission): client = AuthenticatedClient() query = MissionQuery(ids=[mission.id]) - verify_status = kleinkram.core.verify( - client=client, query=query, file_paths=DATA_FILES, skip_hash=True - ) + verify_status = kleinkram.core.verify(client=client, query=query, file_paths=DATA_FILES, skip_hash=True) - assert all( - status == FileVerificationStatus.UPLOADED for status in verify_status.values() - ) + assert all(status == FileVerificationStatus.UPLOADED for status in verify_status.values()) @pytest.mark.slow diff --git a/cli/tests/test_end_to_end.py b/cli/tests/test_end_to_end.py index 1fa752ab9..8d4f63650 100644 --- a/cli/tests/test_end_to_end.py +++ b/cli/tests/test_end_to_end.py @@ -16,7 +16,7 @@ CLI = "klein" PROJECT_NAME = "automated-testing" -DATA_PATH = Path(__file__).parent.parent / "data" / "testing" +DATA_PATH = Path(__file__).parent / "data" @pytest.fixture(scope="session") @@ -48,9 +48,7 @@ def test_upload_verify_update_download_mission(project, tmp_path, api): mission_name = secrets.token_hex(8) upload = f"{CLI} upload -p {project.name} -m {mission_name} --create {DATA_PATH.absolute()}/*.bag" - verify = ( - f"{CLI} verify -p {project.name} -m {mission_name} {DATA_PATH.absolute()}/*.bag" - ) + verify = f"{CLI} verify -p {project.name} -m {mission_name} {DATA_PATH.absolute()}/*.bag" # update = f"{CLI} mission update -p {project.name} -m {mission_name} --metadata {DATA_PATH.absolute()}/metadata.yaml" download = f"{CLI} download -p {project.name} -m {mission_name} --dest {tmp_path.absolute()}" delete_file = f"{CLI} file delete -p {project.name} -m {mission_name} -f {file_names[0].name} -y" @@ -69,7 +67,7 @@ def test_list_files(project, mission, api): assert run_cmd(f"{CLI} list files -p {project.name}") == 0 assert run_cmd(f"{CLI} list files -p {project.name} -m {mission.name}") == 0 assert run_cmd(f"{CLI} list files") == 0 - assert run_cmd(f"{CLI} list files -p {mission.name}") == 0 + assert run_cmd(f"{CLI} list files -p {project.name}") == 0 assert run_cmd(f'{CLI} list files -p "*" -m "*" "*"') == 0 diff --git a/cli/tests/test_fixtures.py b/cli/tests/test_fixtures.py index 3bb0ed208..9c9bc0d8a 100644 --- a/cli/tests/test_fixtures.py +++ b/cli/tests/test_fixtures.py @@ -5,8 +5,8 @@ from kleinkram import list_files from kleinkram import list_missions from kleinkram import list_projects -from testing.backend_fixtures import DATA_FILES -from testing.backend_fixtures import PROJECT_DESCRIPTION +from tests.backend_fixtures import DATA_FILES +from tests.backend_fixtures import PROJECT_DESCRIPTION @pytest.mark.slow @@ -22,9 +22,7 @@ def test_mission_fixture(mission, project): files = list_files(mission_ids=[mission.id]) - assert set([file.name for file in files if file.name.endswith(".bag")]) == set( - [file.name for file in DATA_FILES] - ) + assert set([file.name for file in files if file.name.endswith(".bag")]) == set([file.name for file in DATA_FILES]) @pytest.mark.slow diff --git a/cli/tests/test_printing.py b/cli/tests/test_printing.py index 8b6d31a71..f3c153445 100644 --- a/cli/tests/test_printing.py +++ b/cli/tests/test_printing.py @@ -48,9 +48,7 @@ def test_parse_metadata_value(): mv = MetadataValue(type_=MetadataValueType.BOOLEAN, value="false") assert parse_metadata_value(mv) is False # noqa mv = MetadataValue(type_=MetadataValueType.DATE, value="2021-01-01T00:00:00Z") - assert parse_metadata_value(mv) == datetime.datetime( - 2021, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ) + assert parse_metadata_value(mv) == datetime.datetime(2021, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc) @pytest.mark.skip diff --git a/cli/tests/test_utils.py b/cli/tests/test_utils.py index d2b585b46..73cd319b2 100644 --- a/cli/tests/test_utils.py +++ b/cli/tests/test_utils.py @@ -85,9 +85,7 @@ def test_is_valid_uuid4(): "invalid_sybmols_____.txt", id="invalid symbols", ), - pytest.param( - Path(f'{"a" * 100}.txt'), f'{"a" * 40}38bf3e475f.txt', id="too long" - ), + pytest.param(Path(f'{"a" * 100}.txt'), f'{"a" * 40}38bf3e475f.txt', id="too long"), pytest.param(Path(f'{"a" * 50}.txt'), f'{"a" * 50}.txt', id="max length"), pytest.param(Path("in/a/folder.txt"), "folder.txt", id="in folder"), ], diff --git a/cli/tests/test_wrappers.py b/cli/tests/test_wrappers.py index 11b341c34..c43e6de19 100644 --- a/cli/tests/test_wrappers.py +++ b/cli/tests/test_wrappers.py @@ -12,37 +12,25 @@ def test_args_to_project_query() -> None: assert _args_to_project_query() == ProjectQuery() - assert _args_to_project_query(project_names=["test"]) == ProjectQuery( - patterns=["test"] - ) + assert _args_to_project_query(project_names=["test"]) == ProjectQuery(patterns=["test"]) _id = uuid4() assert _args_to_project_query(project_ids=[_id]) == ProjectQuery(ids=[_id]) - assert _args_to_project_query( - project_names=["test"], project_ids=[_id] - ) == ProjectQuery(patterns=["test"], ids=[_id]) + assert _args_to_project_query(project_names=["test"], project_ids=[_id]) == ProjectQuery(patterns=["test"], ids=[_id]) assert _args_to_project_query(project_ids=[str(_id)]) == ProjectQuery(ids=[_id]) def test_args_to_mission_query() -> None: assert _args_to_mission_query() == MissionQuery() - assert _args_to_mission_query(mission_names=["test"]) == MissionQuery( - patterns=["test"] - ) + assert _args_to_mission_query(mission_names=["test"]) == MissionQuery(patterns=["test"]) _id = uuid4() assert _args_to_mission_query(mission_ids=[_id]) == MissionQuery(ids=[_id]) - assert _args_to_mission_query( - mission_names=["test"], mission_ids=[_id] - ) == MissionQuery(patterns=["test"], ids=[_id]) + assert _args_to_mission_query(mission_names=["test"], mission_ids=[_id]) == MissionQuery(patterns=["test"], ids=[_id]) assert _args_to_mission_query(mission_ids=[str(_id)]) == MissionQuery(ids=[_id]) - assert _args_to_mission_query(project_names=["test"]) == MissionQuery( - project_query=ProjectQuery(patterns=["test"]) - ) - assert _args_to_mission_query(project_ids=[_id]) == MissionQuery( - project_query=ProjectQuery(ids=[_id]) - ) + assert _args_to_mission_query(project_names=["test"]) == MissionQuery(project_query=ProjectQuery(patterns=["test"])) + assert _args_to_mission_query(project_ids=[_id]) == MissionQuery(project_query=ProjectQuery(ids=[_id])) def test_args_to_file_query() -> None: @@ -51,17 +39,11 @@ def test_args_to_file_query() -> None: _id = uuid4() assert _args_to_file_query(file_ids=[_id]) == FileQuery(ids=[_id]) - assert _args_to_file_query(file_names=["test"], file_ids=[_id]) == FileQuery( - patterns=["test"], ids=[_id] - ) + assert _args_to_file_query(file_names=["test"], file_ids=[_id]) == FileQuery(patterns=["test"], ids=[_id]) assert _args_to_file_query(file_ids=[str(_id)]) == FileQuery(ids=[_id]) - assert _args_to_file_query(mission_names=["test"]) == FileQuery( - mission_query=MissionQuery(patterns=["test"]) - ) - assert _args_to_file_query(mission_ids=[_id]) == FileQuery( - mission_query=MissionQuery(ids=[_id]) - ) + assert _args_to_file_query(mission_names=["test"]) == FileQuery(mission_query=MissionQuery(patterns=["test"])) + assert _args_to_file_query(mission_ids=[_id]) == FileQuery(mission_query=MissionQuery(ids=[_id])) assert _args_to_file_query(project_names=["test"]) == FileQuery( mission_query=MissionQuery(project_query=ProjectQuery(patterns=["test"])) diff --git a/common/api/types/actions/docker-image.dto.ts b/common/api/types/actions/docker-image.dto.ts deleted file mode 100644 index 36baeecdf..000000000 --- a/common/api/types/actions/docker-image.dto.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsOptional, IsString } from 'class-validator'; - -export class DockerImageDto { - @ApiProperty() - @IsArray() - @IsString({ each: true }) - repoDigests!: string[]; - - @ApiProperty({ description: 'SHA value', required: false }) - @IsOptional() - @IsString() - sha!: string; -} diff --git a/common/api/types/add-tag-type.dto.ts b/common/api/types/add-tag-type.dto.ts deleted file mode 100644 index 854b8da46..000000000 --- a/common/api/types/add-tag-type.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class AddTagTypeDto {} diff --git a/common/api/types/cancel-processing-response.dto.ts b/common/api/types/cancel-processing-response.dto.ts deleted file mode 100644 index 7713eae78..000000000 --- a/common/api/types/cancel-processing-response.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class CancelProcessingResponseDto {} diff --git a/common/api/types/delete-mission-response.dto.ts b/common/api/types/delete-mission-response.dto.ts deleted file mode 100644 index 02764b1ac..000000000 --- a/common/api/types/delete-mission-response.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class DeleteMissionResponseDto {} diff --git a/common/api/types/file/file-event.dto.ts b/common/api/types/file/file-event.dto.ts deleted file mode 100644 index 178da2584..000000000 --- a/common/api/types/file/file-event.dto.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Type } from 'class-transformer'; -import { - IsArray, - IsDate, - IsEnum, - IsNumber, - IsObject, - IsOptional, - IsUUID, - ValidateNested, -} from 'class-validator'; -import { FileEventType } from '../../../frontend_shared/enum'; -import { UserDto } from '../user.dto'; - -export class FileEventDto { - @IsUUID() - uuid!: string; - - @IsEnum(FileEventType) - type!: FileEventType; - - @IsDate() - @Type(() => Date) - createdAt!: Date; - - @IsObject() - details!: Record; - - @IsOptional() - @ValidateNested() - @Type(() => UserDto) - actor?: UserDto; -} - -export class FileEventsDto { - @IsArray() - @ValidateNested({ each: true }) - @Type(() => FileEventDto) - data!: FileEventDto[]; - - @IsNumber() - count!: number; -} diff --git a/common/api/types/file/file-queue-entry.dto.ts b/common/api/types/file/file-queue-entry.dto.ts deleted file mode 100644 index ad44bcda2..000000000 --- a/common/api/types/file/file-queue-entry.dto.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - IsDate, - IsEnum, - IsNumber, - IsString, - IsUUID, - ValidateNested, -} from 'class-validator'; -import { FileLocation, QueueState } from '../../../frontend_shared/enum'; -import { MissionDto } from '../mission/mission.dto'; -import { UserDto } from '../user.dto'; - -export class FileQueueEntryDto { - @ApiProperty() - @IsUUID() - uuid!: string; - - @ApiProperty() - @IsDate() - createdAt!: Date; - - @ApiProperty() - @IsDate() - updatedAt!: Date; - - @ApiProperty() - @IsString() - identifier!: string; - - @ApiProperty() - @IsString() - display_name!: string; - - @ApiProperty() - @IsEnum(FileLocation) - location!: FileLocation; - - @ApiProperty() - @IsNumber() - processingDuration!: number; - - @ApiProperty() - @ValidateNested() - @Type(() => UserDto) - creator!: UserDto; - - @ApiProperty() - @IsEnum(QueueState) - state!: QueueState; - - @ApiProperty() - @ValidateNested() - @Type(() => MissionDto) - mission!: MissionDto; -} diff --git a/common/api/types/project/delete-project-response.dto.ts b/common/api/types/project/delete-project-response.dto.ts deleted file mode 100644 index d95df0568..000000000 --- a/common/api/types/project/delete-project-response.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class DeleteProjectResponseDto {} diff --git a/common/api/types/queue-active.dto.ts b/common/api/types/queue-active.dto.ts deleted file mode 100644 index c0e74978d..000000000 --- a/common/api/types/queue-active.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class QueueActiveDto {} diff --git a/common/api/types/queue/bull-queue.dto.ts b/common/api/types/queue/bull-queue.dto.ts deleted file mode 100644 index d215aab83..000000000 --- a/common/api/types/queue/bull-queue.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class BullQueueDto {} diff --git a/common/api/types/queue/stop-job-response.dto.ts b/common/api/types/queue/stop-job-response.dto.ts deleted file mode 100644 index b211a7ac3..000000000 --- a/common/api/types/queue/stop-job-response.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class StopJobResponseDto {} diff --git a/common/api/types/remove-tag-type.dto.ts b/common/api/types/remove-tag-type.dto.ts deleted file mode 100644 index d4fe2c198..000000000 --- a/common/api/types/remove-tag-type.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class RemoveTagTypeDto {} diff --git a/common/api/types/tags/add-tags.dto.ts b/common/api/types/tags/add-tags.dto.ts deleted file mode 100644 index 5b48db1f4..000000000 --- a/common/api/types/tags/add-tags.dto.ts +++ /dev/null @@ -1,2 +0,0 @@ -export class AddTagsDto {} -export class AddTagDto {} diff --git a/common/api/types/tags/delete-tag.dto.ts b/common/api/types/tags/delete-tag.dto.ts deleted file mode 100644 index b9776ddd6..000000000 --- a/common/api/types/tags/delete-tag.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class DeleteTagDto {} diff --git a/common/api/types/update-tag-type.dto.ts b/common/api/types/update-tag-type.dto.ts deleted file mode 100644 index 77cf1e01c..000000000 --- a/common/api/types/update-tag-type.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class UpdateTagTypeDto {} diff --git a/common/api/types/upload.dto.ts b/common/api/types/upload.dto.ts deleted file mode 100644 index 9c311fb13..000000000 --- a/common/api/types/upload.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class FileUploadDto {} diff --git a/common/api/types/user.dto.ts b/common/api/types/user.dto.ts deleted file mode 100644 index 3d430c2dd..000000000 --- a/common/api/types/user.dto.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - IsBoolean, - IsDate, - IsEmail, - IsEnum, - IsOptional, - IsString, - IsUUID, - registerDecorator, - ValidateIf, - ValidateNested, - ValidationOptions, -} from 'class-validator'; -import { AccessGroupType, UserRole } from '../../frontend_shared/enum'; -import { ProjectWithAccessRightsDto } from './project/project-access.dto'; - -const IsNotUndefined = (validationOptions?: ValidationOptions) => { - return function (object: object, propertyName: string): void { - registerDecorator({ - name: 'isNotUndefined', - target: object.constructor, - propertyName: propertyName, - options: validationOptions ?? {}, - validator: { - validate(value: unknown): Promise | boolean { - return value !== undefined; - }, - defaultMessage(): string { - return '$property must not be undefined'; - }, - }, - }); - }; -}; - -export class UserDto { - @ApiProperty() - @IsUUID() - uuid!: string; - - @ApiProperty() - @IsString() - name!: string; - - @ApiProperty() - @IsNotUndefined() - @IsOptional() - @IsString() - avatarUrl!: string | null; - - @ApiProperty({ - description: - 'Email address of the user. Set to null if not available or not accessible.', - nullable: true, - required: false, - }) - @IsNotUndefined() - @IsOptional() - @IsEmail() - email!: string | null; -} - -export class AccessGroupDto { - @ApiProperty() - @IsUUID() - uuid!: string; - - @ApiProperty() - @IsString() - name!: string; - - @ApiProperty() - @IsDate() - createdAt!: Date; - - @ApiProperty() - @IsDate() - updatedAt!: Date; - - @ApiProperty() - @IsEnum(AccessGroupType) - type!: AccessGroupType; - - @ApiProperty() - @IsBoolean() - hidden!: boolean; - - @ApiProperty() - @IsNotUndefined() - @IsOptional() - @ValidateNested() - @Type(() => UserDto) - creator!: UserDto | null; - - @ApiProperty() - @ValidateNested({ each: true }) - @Type(() => GroupMembershipDto) - memberships!: GroupMembershipDto[]; - - @ApiProperty() - @ValidateNested({ each: true }) - @Type(() => ProjectWithAccessRightsDto) - projectAccesses!: ProjectWithAccessRightsDto[]; -} - -export class GroupMembershipDto { - @ApiProperty() - @IsUUID() - uuid!: string; - - @ApiProperty() - @IsDate() - createdAt!: Date; - - @ValidateIf((_, value) => { - console.log(value, typeof value); - return true; - }) - @ApiProperty() - @IsDate() - updatedAt!: Date; - - @ValidateIf((_, value) => { - console.log(value, typeof value); - return value !== null; - }) - @ApiProperty() - @IsDate() - expirationDate!: Date | null; - - @ApiProperty() - @ValidateNested() - @Type(() => UserDto) - user!: UserDto; - - @ApiProperty() - @IsBoolean() - canEditGroup!: boolean; - - @ApiProperty({ - type: () => AccessGroupDto, - description: 'Access Group', - nullable: true, - }) - @IsNotUndefined() - @IsOptional() - @ValidateNested() - @Type(() => AccessGroupDto) - accessGroup!: AccessGroupDto | null; -} - -export class CurrentAPIUserDto extends UserDto { - @ApiProperty({ - type: () => [GroupMembershipDto], - description: 'List of group memberships', - }) - @ValidateNested({ each: true }) - @Type(() => GroupMembershipDto) - memberships!: GroupMembershipDto[]; - - @ApiProperty() - @IsEnum(UserRole) - role!: UserRole; -} - -export class UsersDto { - @ApiProperty({ - type: () => [UserDto], - description: 'List of users', - }) - users!: UserDto[]; - - @ApiProperty() - count!: number; -} diff --git a/common/audit/audit.types.ts b/common/audit/audit.types.ts deleted file mode 100644 index 31b36c8e8..000000000 --- a/common/audit/audit.types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import UserEntity from '@common/entities/user/user.entity'; -import { FileEventType } from '@common/frontend_shared/enum'; - -export type AuditActionType = FileEventType; - -export interface AuditContext { - fileUuid?: string; - filename?: string; - missionUuid?: string; - actor?: UserEntity; - details?: Record; -} - -export type AuditContextExtractor = (arguments_: any[]) => AuditContext; diff --git a/common/entities/action/action.entity.ts b/common/entities/action/action.entity.ts deleted file mode 100644 index 3f2e695cf..000000000 --- a/common/entities/action/action.entity.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; -import { ActionState, ArtifactState } from '../../frontend_shared/enum'; -import { RuntimeDescription } from '../../types'; -import ApikeyEntity from '../auth/apikey.entity'; -import BaseEntity from '../base-entity.entity'; -import MissionEntity from '../mission/mission.entity'; -import UserEntity from '../user/user.entity'; -import WorkerEntity from '../worker/worker.entity'; -import ActionTemplateEntity from './action-template.entity'; - -export interface ContainerLog { - timestamp: string; - message: string; - type: 'stdout' | 'stderr'; -} - -export interface Image { - sha: string | null; - repoDigests: string[] | null; -} - -export interface Container { - id: string; -} - -export interface SubmittedAction { - uuid: string; - state: ActionState; - - runtime_requirements: RuntimeDescription; - image: Image; - command: string; -} - -@Entity({ name: 'action' }) -export default class ActionEntity extends BaseEntity { - @Column() - state!: ActionState; - - @Column({ type: 'json', nullable: true }) - container?: Container; - - @ManyToOne(() => UserEntity, (user) => user.submittedActions, { - nullable: false, - }) - creator?: UserEntity; - - @Column({ nullable: true }) - state_cause?: string; - - @Column({ nullable: true }) - executionStartedAt?: Date; - - @Column({ nullable: true }) - executionEndedAt?: Date; - - @ManyToOne(() => MissionEntity, (mission) => mission.actions, { - onDelete: 'CASCADE', - nullable: false, - }) - mission?: MissionEntity; - - @Column({ type: 'json', nullable: true }) - logs?: ContainerLog[]; - - @Column({ type: 'json', nullable: true, default: [] }) - auditLogs?: any[]; - - @Column({ nullable: true }) - exit_code?: number; - - @Column({ nullable: true }) - artifact_path?: string; - - @Column({ nullable: false, default: ArtifactState.AWAITING_ACTION }) - artifacts!: ArtifactState; - - @OneToOne(() => ApikeyEntity, (apikey) => apikey.action) - @JoinColumn() - key?: ApikeyEntity; - - @ManyToOne( - () => ActionTemplateEntity, - (actionTemplate) => actionTemplate.actions, - { nullable: false }, - ) - template?: ActionTemplateEntity; - - @Column({ type: 'json', nullable: true }) - image?: Image; - - @ManyToOne(() => WorkerEntity, (worker) => worker.actions, { - nullable: true, - }) - worker?: WorkerEntity; -} diff --git a/common/entities/auth/mission-access.entity.ts b/common/entities/auth/mission-access.entity.ts deleted file mode 100644 index 3d5590503..000000000 --- a/common/entities/auth/mission-access.entity.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Column, Entity, ManyToOne, Unique } from 'typeorm'; -import BaseEntity from '../base-entity.entity'; -import MissionEntity from '../mission/mission.entity'; -import AccessGroupEntity from './accessgroup.entity'; - -import { AccessGroupRights } from '../../frontend_shared/enum'; - -@Entity({ name: 'mission_access' }) -@Unique('no_duplicated_access_groups_per_mission', ['accessGroup', 'mission']) -export default class MissionAccessEntity extends BaseEntity { - @Column() - rights!: AccessGroupRights; - - @ManyToOne(() => AccessGroupEntity, (group) => group.project_accesses, { - nullable: false, - }) - accessGroup?: AccessGroupEntity; - - @ManyToOne(() => MissionEntity, (mission) => mission.mission_accesses, { - nullable: false, - }) - mission?: MissionEntity; -} diff --git a/common/entities/file/file.entity.ts b/common/entities/file/file.entity.ts deleted file mode 100644 index 303e96176..000000000 --- a/common/entities/file/file.entity.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - Column, - Entity, - JoinTable, - ManyToMany, - ManyToOne, - OneToMany, - Unique, -} from 'typeorm'; -import { FileOrigin, FileState, FileType } from '../../frontend_shared/enum'; -import BaseEntity from '../base-entity.entity'; -import CategoryEntity from '../category/category.entity'; -import MissionEntity from '../mission/mission.entity'; -import TopicEntity from '../topic/topic.entity'; -import UserEntity from '../user/user.entity'; - -@Entity({ name: 'file_entity' }) -@Unique('unique_file_name_per_mission', ['filename', 'mission']) -export default class FileEntity extends BaseEntity { - @ManyToOne(() => MissionEntity, (mission) => mission.files, { - nullable: false, - }) - mission?: MissionEntity; - - @Column() - date!: Date; - - @OneToMany(() => TopicEntity, (topic) => topic.file) - topics?: TopicEntity[]; - - @Column() - filename!: string; - - @Column({ - type: 'bigint', - transformer: { - to: (value: number) => value, - from: (value: string) => Number.parseInt(value, 10), - }, - }) - size?: number; - - /** - * The user who uploaded the file. - */ - @ManyToOne(() => UserEntity, (user) => user.files, { nullable: false }) - creator?: UserEntity; - - @Column() - type!: FileType; - - @Column({ default: FileState.OK }) - state!: FileState; - - @Column({ nullable: true }) - hash?: string; - - @ManyToMany(() => CategoryEntity, (category) => category.files) - @JoinTable() - categories?: CategoryEntity[]; - - /** - * The parent file this file was derived from. - * e.g., If this is a .mcap converted from a .bag, the .bag is the parent. - */ - @ManyToOne(() => FileEntity, (file) => file.derivedFiles, { - nullable: true, - onDelete: 'SET NULL', - }) - parent?: FileEntity; - - /** - * Files derived from this file. - */ - @OneToMany(() => FileEntity, (file) => file.parent) - derivedFiles?: FileEntity[]; - - @Column({ nullable: true }) - origin?: FileOrigin; -} diff --git a/common/entities/metadata/metadata.entity.ts b/common/entities/metadata/metadata.entity.ts deleted file mode 100644 index ba4da593a..000000000 --- a/common/entities/metadata/metadata.entity.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column, Entity, ManyToOne } from 'typeorm'; -import BaseEntity from '../base-entity.entity'; -import MissionEntity from '../mission/mission.entity'; -import TagTypeEntity from '../tagType/tag-type.entity'; -import UserEntity from '../user/user.entity'; - -// TODO: rename the SQL table from tag to metadata -// in some early version of kleinkram metadata were named -// tags, this is a legacy and should be cleaned up at some point -@Entity({ name: 'tag' }) -export default class MetadataEntity extends BaseEntity { - @Column({ nullable: true, name: 'STRING' }) - value_string?: string; - - @Column({ nullable: true, type: 'double precision', name: 'NUMBER' }) - value_number?: number; - - @Column({ nullable: true, name: 'BOOLEAN' }) - value_boolean?: boolean; - - @Column({ nullable: true, name: 'DATE' }) - value_date?: Date; - - @Column({ nullable: true, name: 'LOCATION' }) - value_location?: string; - - @ManyToOne(() => MissionEntity, (mission) => mission.tags, { - onDelete: 'CASCADE', - }) - mission?: MissionEntity; - - @ManyToOne(() => TagTypeEntity, (tagType) => tagType.tags, { eager: true }) - tagType?: TagTypeEntity; - - @ManyToOne(() => UserEntity, (user) => user.tags, { - onDelete: 'SET NULL', - nullable: true, - }) - creator?: UserEntity; -} diff --git a/common/entities/mission/mission.entity.ts b/common/entities/mission/mission.entity.ts deleted file mode 100644 index af465e42d..000000000 --- a/common/entities/mission/mission.entity.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; -import ActionEntity from '../action/action.entity'; -import ApikeyEntity from '../auth/apikey.entity'; -import MissionAccessEntity from '../auth/mission-access.entity'; -import BaseEntity from '../base-entity.entity'; -import FileEntity from '../file/file.entity'; -import IngestionJobEntity from '../file/ingestion-job.entity'; -import MetadataEntity from '../metadata/metadata.entity'; -import ProjectEntity from '../project/project.entity'; -import UserEntity from '../user/user.entity'; - -@Unique('unique_mission_name_per_project', ['name', 'project']) -@Entity({ name: 'mission' }) -export default class MissionEntity extends BaseEntity { - @Column() - name!: string; - - @ManyToOne(() => ProjectEntity, (project) => project.missions, { - nullable: false, - }) - project?: ProjectEntity; - - @OneToMany(() => FileEntity, (file) => file.mission) - files?: FileEntity[]; - - @OneToMany(() => ActionEntity, (action) => action.mission) - actions?: ActionEntity[]; - - @OneToMany(() => IngestionJobEntity, (queue) => queue.mission) - ingestionJobs?: IngestionJobEntity[]; - - @ManyToOne(() => UserEntity, (user) => user.missions, { nullable: false }) - creator?: UserEntity; - - @OneToMany(() => ApikeyEntity, (apiKey) => apiKey.mission) - api_keys?: ApikeyEntity[]; - - @OneToMany( - () => MissionAccessEntity, - (missionAccess) => missionAccess.mission, - ) - mission_accesses?: MissionAccessEntity[]; - - @OneToMany(() => MetadataEntity, (tag) => tag.mission) - tags?: MetadataEntity[]; - - fileCount?: number; - size?: number; -} diff --git a/common/entities/project/project.entity.ts b/common/entities/project/project.entity.ts deleted file mode 100644 index 140d09d61..000000000 --- a/common/entities/project/project.entity.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Column, Entity, ManyToMany, ManyToOne, OneToMany } from 'typeorm'; -import ProjectAccessEntity from '../auth/project-access.entity'; -import BaseEntity from '../base-entity.entity'; -import CategoryEntity from '../category/category.entity'; -import MissionEntity from '../mission/mission.entity'; -import TagTypeEntity from '../tagType/tag-type.entity'; -import UserEntity from '../user/user.entity'; - -@Entity({ name: 'project' }) -export default class ProjectEntity extends BaseEntity { - /** - * The name of the project. This is the name that will be displayed in the UI. - * The name must be globally unique. - */ - @Column({ unique: true }) - name!: string; - - @OneToMany(() => MissionEntity, (mission) => mission.project) - missions?: MissionEntity[]; - readonly missionCount?: number; - - @OneToMany( - () => ProjectAccessEntity, - (projectAccess) => projectAccess.project, - { - cascade: true, - }, - ) - project_accesses?: ProjectAccessEntity[]; - - @Column() - description!: string; - - @ManyToOne(() => UserEntity, (user) => user.projects, { nullable: false }) - creator?: UserEntity; - - @ManyToMany(() => TagTypeEntity, (tag) => tag.project, { - onDelete: 'CASCADE', - nullable: false, - }) - requiredTags!: TagTypeEntity[]; - - @OneToMany(() => CategoryEntity, (category) => category.project) - categories?: CategoryEntity[]; - - @Column({ default: false }) - autoConvert?: boolean; -} diff --git a/common/entities/tagType/tag-type.entity.ts b/common/entities/tagType/tag-type.entity.ts deleted file mode 100644 index 8c888c9d4..000000000 --- a/common/entities/tagType/tag-type.entity.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Column, Entity, JoinTable, ManyToMany, OneToMany } from 'typeorm'; -import { DataType } from '../../frontend_shared/enum'; -import BaseEntity from '../base-entity.entity'; -import MetadataEntity from '../metadata/metadata.entity'; -import ProjectEntity from '../project/project.entity'; - -@Entity({ name: 'tag_type' }) -export default class TagTypeEntity extends BaseEntity { - @Column() - name!: string; - - @Column({ nullable: true }) - description?: string; - - @Column() - datatype!: DataType; - - @ManyToMany(() => ProjectEntity, (project) => project.requiredTags) - @JoinTable() - project?: ProjectEntity; - - @OneToMany(() => MetadataEntity, (tag) => tag.tagType) - tags?: MetadataEntity[]; -} diff --git a/common/entities/user/user.entity.ts b/common/entities/user/user.entity.ts deleted file mode 100644 index 9b5c4a912..000000000 --- a/common/entities/user/user.entity.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Column, Entity, JoinColumn, OneToMany, OneToOne } from 'typeorm'; -import { UserRole } from '../../frontend_shared/enum'; -import ActionTemplateEntity from '../action/action-template.entity'; -import ActionEntity from '../action/action.entity'; -import AccountEntity from '../auth/account.entity'; -import ApikeyEntity from '../auth/apikey.entity'; -import GroupMembershipEntity from '../auth/group-membership.entity'; -import BaseEntity from '../base-entity.entity'; -import CategoryEntity from '../category/category.entity'; -import FileEntity from '../file/file.entity'; -import IngestionJobEntity from '../file/ingestion-job.entity'; -import MetadataEntity from '../metadata/metadata.entity'; -import MissionEntity from '../mission/mission.entity'; -import ProjectEntity from '../project/project.entity'; - -@Entity({ name: 'user' }) -export default class UserEntity extends BaseEntity { - /** - * The name of the user. This is the name that will be displayed in the UI. - * The name gets automatically extracted from the oauth provider. - * - * @example 'John Doe' - */ - @Column() - name!: string; - - /** - * The email of the user. This is the email that will be displayed in the UI. - * The email gets automatically extracted from the oauth provider. - * - * @example 'john.doe@example.com' - * - * The email is unique and cannot be changed. - * - */ - @Column({ unique: true, select: false, update: false }) - email?: string; - - /** - * The role of the user. The role determines what the user can do in the application. - * - * @example 'USER' - * - * @see UserRole - * - */ - @Column({ - select: true, - type: 'enum', - enum: UserRole, - default: UserRole.USER, - }) - role?: UserRole; - - /** - * A hidden user is not returned in any search queries. - * Hidden users may still be accessed by their UUID (e.g., when - * listing group memberships). - * - */ - @Column({ - select: false, - default: false, - }) - hidden?: boolean; - - /** - * The avatar url of the user. This is the url of the avatar that will be displayed in the UI. - * The avatar url gets automatically extracted from the oauth provider. - * - * @example 'https://example.com/avatar.jpg' - */ - @Column({ nullable: true }) - avatarUrl?: string; - - @OneToOne(() => AccountEntity, (account) => account.user) - @JoinColumn({ name: 'account_uuid' }) - account?: AccountEntity; - - @OneToMany(() => GroupMembershipEntity, (membership) => membership.user) - memberships?: GroupMembershipEntity[]; - - @OneToMany(() => ProjectEntity, (project) => project.creator) - projects?: ProjectEntity[]; - - @OneToMany(() => MissionEntity, (mission) => mission.creator) - missions?: MissionEntity[]; - - @OneToMany(() => FileEntity, (file) => file.creator) - files?: FileEntity[]; - - @OneToMany(() => IngestionJobEntity, (queue) => queue.creator) - queues?: IngestionJobEntity[]; - - @OneToMany(() => ActionEntity, (action) => action.mission) - submittedActions?: ActionEntity[]; - - @OneToMany( - () => ActionTemplateEntity, - (actionTemplate) => actionTemplate.creator, - ) - templates?: ActionTemplateEntity[]; - - @OneToMany(() => MetadataEntity, (tag) => tag.creator) - tags?: MetadataEntity[]; - - @OneToMany(() => ApikeyEntity, (apikey) => apikey.user) - api_keys?: ApikeyEntity[]; - - @OneToMany(() => CategoryEntity, (category) => category.creator) - categories?: CategoryEntity[]; -} diff --git a/common/environment.ts b/common/environment.ts deleted file mode 100644 index 0a4cc3197..000000000 --- a/common/environment.ts +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Ensures extracted environment variable is a string - * - * @param value - extracted environment variable - * @returns environment variable as string - */ -function asString(value: string | undefined): string { - if (value === undefined) { - const message = 'The environment variable cannot be "undefined".'; - throw new Error(message); - } - - return value; -} - -/** - * Ensures extracted environment variable is a number - * - * @param value - extracted environment variable - * @returns environment variable as integer - */ -function asNumber(value: string | undefined): number { - const stringValue = asString(value); - const numberValue = Number.parseFloat(stringValue); - - if (Number.isNaN(numberValue)) { - const message = `The environment variable has to hold a stringified number value - not ${stringValue}`; - throw new Error(message); - } - - return numberValue; -} - -/** - * Ensures extracted environment variable is a boolean - * - * @param value - extracted environment variable - * @returns environment variable as boolean - */ -function asBoolean(value: string | undefined): boolean { - const stringVariable = asString(value); - if (!(stringVariable === 'true' || stringVariable === 'false')) { - const message = `The environment variable has to hold a stringified boolean value - not ${stringVariable}`; - throw new Error(message); - } - return stringVariable === 'true'; -} - -export default { - /** - * @returns database name - */ - get DB_DATABASE(): string { - return asString(process.env['DB_DATABASE']); - }, - /** - * @returns database admin user - */ - get DB_USER(): string { - return asString(process.env['DB_USER']); - }, - /** - * @returns database admin password - */ - get DB_PASSWORD(): string { - return asString(process.env['DB_PASSWORD']); - }, - /** - * @returns database port - */ - get DB_PORT(): number { - return asNumber(process.env['DB_PORT']); - }, - /** - * @returns database host name - * @example database - */ - get DB_HOST(): string { - return asString(process.env['DB_HOST']); - }, - /** - * @returns whether application runs in development mode - */ - get DEV(): boolean { - return asBoolean(process.env['DEV']); - }, - - /** - * @returns glob describing where typeorm entities are found - * @example dist/entities/*.entities.js - */ - get ENTITIES(): string { - return asString(process.env['ENTITIES']); - }, - /** - * @returns backend port for lambda functions - */ - get SERVER_PORT(): number { - return asNumber(process.env['SERVER_PORT']); - }, - /** - * @returns base url of frontend - */ - get BASE_URL(): string { - return asString(process.env['BASE_URL']); - }, - - /** - * @returns name of project - */ - get PROJECT_NAME(): string { - return asString(process.env['PROJECT_NAME']); - }, - - get MINIO_ACCESS_KEY(): string { - return asString(process.env['MINIO_ACCESS_KEY']); - }, - - get MINIO_SECRET_KEY(): string { - return asString(process.env['MINIO_SECRET_KEY']); - }, - - get MINIO_DATA_BUCKET_NAME(): string { - return asString(process.env['MINIO_DATA_BUCKET_NAME']); - }, - get MINIO_DB_BUCKET_NAME(): string { - return asString(process.env['MINIO_DB_BUCKET_NAME']); - }, - get MINIO_ENDPOINT(): string { - return asString(process.env['MINIO_ENDPOINT']); - }, - - get MINIO_USER(): string { - return asString(process.env['MINIO_USER']); - }, - - get MINIO_PASSWORD(): string { - return asString(process.env['MINIO_PASSWORD']); - }, - - get GOOGLE_CLIENT_ID(): string { - return asString(process.env['GOOGLE_CLIENT_ID']); - }, - get GOOGLE_CLIENT_SECRET(): string { - return asString(process.env['GOOGLE_CLIENT_SECRET']); - }, - - get GITHUB_CLIENT_ID(): string { - return asString(process.env['GITHUB_CLIENT_ID']); - }, - - get GITHUB_CLIENT_SECRET(): string { - return asString(process.env['GITHUB_CLIENT_SECRET']); - }, - - get JWT_SECRET(): string { - return asString(process.env['JWT_SECRET']); - }, - - get ENDPOINT(): string { - return asString(process.env['QUASAR_ENDPOINT']); - }, - - get FRONTEND_URL(): string { - return asString(process.env['FRONTEND_URL']); - }, - get GOOGLE_KEY_FILE(): string { - return asString(process.env['GOOGLE_KEY_FILE']); - }, - - get ARTIFACTS_UPLOADER_IMAGE(): string { - return asString(process.env['ARTIFACTS_UPLOADER_IMAGE']); - }, - - get MINIO_ARTIFACTS_BUCKET_NAME(): string { - return asString(process.env['MINIO_ARTIFACTS_BUCKET_NAME']); - }, - - get DOCS_URL(): string { - return asString(process.env['DOCS_URL']); - }, - - get VITE_USE_FAKE_OAUTH_FOR_DEVELOPMENT(): boolean { - return asBoolean(process.env['VITE_USE_FAKE_OAUTH_FOR_DEVELOPMENT']); - }, - - /** - * @returns Docker Hub namespace for image validation (optional) - * @example rslethz/ - */ - get DOCKER_HUB_NAMESPACE(): string { - return process.env['VITE_DOCKER_HUB_NAMESPACE'] ?? ''; - }, -}; diff --git a/common/factories/auth/project-access.factory.ts b/common/factories/auth/project-access.factory.ts deleted file mode 100644 index 6e46f4016..000000000 --- a/common/factories/auth/project-access.factory.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { define } from 'typeorm-seeding'; -import AccessGroupEntity from '../../entities/auth/accessgroup.entity'; -import ProjectAccessEntity from '../../entities/auth/project-access.entity'; -import ProjectEntity from '../../entities/project/project.entity'; - -import { AccessGroupRights } from '../../frontend_shared/enum'; - -export interface ProjectAccessFactoryContext { - projects: ProjectEntity[]; - accessGroups: AccessGroupEntity[]; -} - -// @ts-ignore -define(ProjectAccessEntity, (_, context: ProjectAccessFactoryContext) => { - const projectAccess = new ProjectAccessEntity(); - - projectAccess.rights = faker.helpers.arrayElement([ - 0, 10, 20, 30, - ]) as AccessGroupRights; - - projectAccess.project = faker.helpers.arrayElement(context.projects); - projectAccess.accessGroup = faker.helpers.arrayElement( - context.accessGroups, - ); - return projectAccess; -}); diff --git a/common/factories/file/file.factory.ts b/common/factories/file/file.factory.ts deleted file mode 100644 index f367bfd1b..000000000 --- a/common/factories/file/file.factory.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { define } from 'typeorm-seeding'; -import FileEntity from '../../entities/file/file.entity'; -import MissionEntity from '../../entities/mission/mission.entity'; -import UserEntity from '../../entities/user/user.entity'; -import { extendedFaker } from '../../faker-extended'; -import { FileState } from '../../frontend_shared/enum'; - -export interface FileContext { - mission: MissionEntity; - user: UserEntity; -} - -define(FileEntity, (_, context: Partial = {}) => { - if (!context.mission) { - throw new Error('Mission is required'); - } - - if (!context.user) { - throw new Error('User is required'); - } - - const file = new FileEntity(); - file.date = extendedFaker.date.recent(); - file.type = extendedFaker.ros.fileType(); - const uniqueSuffix = extendedFaker.string.alpha({ length: 8 }); - file.filename = `${extendedFaker.ros.fileName(file.type)}_${uniqueSuffix}`; - file.mission = context.mission; - file.size = extendedFaker.number.int({ min: 0, max: 2e12 }); // 0 bytes to 2 TB - file.creator = context.user; - file.state = FileState.OK; - file.topics = []; - return file; -}); diff --git a/common/factories/user/user.factory.ts b/common/factories/user/user.factory.ts deleted file mode 100644 index 561ad2839..000000000 --- a/common/factories/user/user.factory.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { define } from 'typeorm-seeding'; -import AccessGroupEntity from '../../entities/auth/accessgroup.entity'; -import GroupMembershipEntity from '../../entities/auth/group-membership.entity'; -import UserEntity from '../../entities/user/user.entity'; -import { extendedFaker } from '../../faker-extended'; -import { UserRole } from '../../frontend_shared/enum'; - -export interface UserContext { - firstName: string; - lastName: string; - mail: string; - role: UserRole; - defaultGroupIds: string[]; -} - -define(UserEntity, (_, context: Partial = {}) => { - const role = - context.role ?? - extendedFaker.helpers.arrayElement([UserRole.ADMIN, UserRole.USER]); - const firstName = context.firstName || extendedFaker.person.firstName(); - const lastName = context.lastName || extendedFaker.person.lastName(); - const mail = - context.mail || extendedFaker.internet.email({ firstName, lastName }); - - const user = new UserEntity(); - user.name = `${firstName} ${lastName}`; - user.email = mail; - user.role = role; - user.avatarUrl = extendedFaker.image.avatarGitHub(); - user.uuid = extendedFaker.string.uuid(); - - if (context.defaultGroupIds) { - // TODO: fix... - user.memberships = context.defaultGroupIds.map((id) => { - const accessGroup = new AccessGroupEntity(); - accessGroup.uuid = id; - return accessGroup; - }) as unknown as GroupMembershipEntity[]; - } - - return user; -}); diff --git a/common/frontend_shared/universal-http-reader.ts b/common/frontend_shared/universal-http-reader.ts deleted file mode 100644 index 39088963c..000000000 --- a/common/frontend_shared/universal-http-reader.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { IReadable } from '@mcap/core/dist/cjs/src/types'; - -export class UniversalHttpReader implements IReadable { - private url: string; - private _size: number | undefined; - private additionalHeaders: Record; - - constructor(url: string, additionalHeaders: Record = {}) { - this.url = url; - this.additionalHeaders = additionalHeaders; - } - - async init(): Promise { - const response = await fetch(this.url, { - method: 'HEAD', - headers: this.additionalHeaders, - }); - - if (!response.ok) { - const getResponse = await fetch(this.url, { - headers: { - Range: 'bytes=0-0', - ...this.additionalHeaders, - }, - }); - if (!getResponse.ok) - throw new Error( - `Failed to access file: ${getResponse.statusText}`, - ); - - const range = getResponse.headers.get('Content-Range'); - if (range) { - this._size = Number.parseInt(range.split('/')[1] || '0', 10); - return; - } - } - - const length = response.headers.get('Content-Length'); - if (length) { - this._size = Number.parseInt(length, 10); - } else { - throw new Error('Could not determine file size'); - } - } - - async size(): Promise { - if (this._size === undefined) await this.init(); - return BigInt(this._size ?? 0); - } - - get sizeBytes(): number { - return this._size ?? 0; - } - - async read(offset: bigint, length: bigint): Promise { - const start = Number(offset); - const end = start + Number(length) - 1; - - const response = await fetch(this.url, { - headers: { - Range: `bytes=${start}-${end}`, - ...this.additionalHeaders, - }, - }); - - if (!response.ok) { - throw new Error(`Read failed: ${response.statusText}`); - } - - return new Uint8Array(await response.arrayBuffer()); - } -} diff --git a/common/ormconfig.ts b/common/ormconfig.ts deleted file mode 100644 index 2c7f2a9e0..000000000 --- a/common/ormconfig.ts +++ /dev/null @@ -1,18 +0,0 @@ -import process from 'node:process'; - -export default { - type: 'postgres', - host: process.env['DB_HOST'], - port: Number.parseInt(process.env['DB_PORT'] ?? '5432', 10), - ssl: process.env['DB_SSL'] === 'true', - username: process.env['DB_USER'], - password: process.env['DB_PASSWORD'], - database: process.env['DB_DATABASE'], - synchronize: false, - entities: [ - 'entities/**/*.entity{.ts,.js}', - 'viewEntities/**/*.entity{.ts,.js}', - ], - seeds: ['seeds/**/*.seed{.ts,.js}'], - factories: ['factories/**/*.factory{.ts,.js}'], -}; diff --git a/common/package.json b/common/package.json deleted file mode 100644 index 916663ea4..000000000 --- a/common/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "kleinkram-common", - "version": "0.55.2", - "license": "MIT", - "scripts": { - "seed:config": "ts-node ./node_modules/typeorm-seeding/dist/cli.js config", - "seed:run": "ts-node ./node_modules/typeorm-seeding/dist/cli.js seed" - }, - "dependencies": { - "@faker-js/faker": "^10.1.0", - "@mcap/core": "^2.1.7", - "@nestjs/common": "^10.4.20", - "@nestjs/core": "^11.1.9", - "@nestjs/schedule": "^6.0.1", - "@nestjs/swagger": "^11.2.2", - "@nestjs/typeorm": "^10.0.2", - "@willsoto/nestjs-prometheus": "^6.0.2", - "axios": "^1.13.2", - "bull": "^4.16.4", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.2", - "jsonwebtoken": "^9.0.2", - "minio": "8.0.6", - "pg": "^8.16.3", - "prom-client": "^15.1.3", - "rxjs": "^7.8.2", - "ts-node": "^10.9.2", - "typeorm": "^0.3.27", - "typeorm-seeding": "^1.6.1" - }, - "devDependencies": { - "@types/node": "^24.10.1", - "typescript": "^5.9.3" - } -} diff --git a/common/seeds/user/create-users.seed.ts b/common/seeds/user/create-users.seed.ts deleted file mode 100644 index 7e0544778..000000000 --- a/common/seeds/user/create-users.seed.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Connection, Not } from 'typeorm'; -import { Factory, Seeder } from 'typeorm-seeding'; -import AccessGroupEntity from '../../entities/auth/accessgroup.entity'; -import ProjectAccessEntity from '../../entities/auth/project-access.entity'; -import FileEntity from '../../entities/file/file.entity'; -import MissionEntity from '../../entities/mission/mission.entity'; -import ProjectEntity from '../../entities/project/project.entity'; -import TagTypeEntity from '../../entities/tagType/tag-type.entity'; -import TopicEntity from '../../entities/topic/topic.entity'; -import UserEntity from '../../entities/user/user.entity'; -import { AccessGroupFactoryContext } from '../../factories/auth/accessgroup.factory'; -import { ProjectContext } from '../../factories/project/project.factory'; -import { UserContext } from '../../factories/user/user.factory'; -import { extendedFaker } from '../../faker-extended'; -import { FileType, UserRole } from '../../frontend_shared/enum'; - -export default class CreateUsers implements Seeder { - // Settings for the Seeder - - private USER_COUNT = 10; - private ACCESS_GROUP_COUNT = 20; - private PROJECT_COUNT = 10; - private GROUP_ACCESS_COUNT = 2; - private SEED_ADMINS = true; - private TAG_TYPE_COUNT = 10; - - public async run(factory: Factory, conn: Connection): Promise { - // ////////////////////////////////////////// - // Abort Seeding if Users already exist - // ////////////////////////////////////////// - const usersCount = await conn.getRepository(UserEntity).count({ - where: { uuid: Not('10000000-0000-0000-0000-000000000000') }, - }); - if (usersCount > 0) { - console.log( - '\n\n »» Users already exist in the DB; skipping seeding\n\n', - ); - return; - } - console.log('\n\n »» Seeding Users...\n\n'); - - // ////////////////////////////////////////// - // Start Seeding - // ////////////////////////////////////////// - - // Generate Metadata... - const tagTypes = await factory(TagTypeEntity)({}).createMany( - this.TAG_TYPE_COUNT, - ); - - // Generate Users... - const adminMails = ['admin@kleinkram.leggedrobotics.com']; - - // generate admin users - // and add them to the affiliation group - const adminUsers = - (await Promise.all( - adminMails.map((mail) => - factory(UserEntity)({ - mail, - role: this.SEED_ADMINS ? UserRole.ADMIN : UserRole.USER, - defaultGroupIds: [ - '00000000-0000-0000-0000-000000000000', - ], - } as UserContext).create(), - ), - ) - // catch any errors during user creation - .catch(console.error)) ?? []; - - // generate remaining users - const remainingUserCount = this.USER_COUNT - adminMails.length; - const users = - (await factory(UserEntity)({ - role: UserRole.USER, - }) - .createMany(remainingUserCount) - // catch any errors during user creation - .catch(console.error)) ?? []; - - const allUsers = [...adminUsers, ...users]; - - // create personal access groups - await Promise.all( - allUsers.map((user) => - factory(AccessGroupEntity)({ - user: user, - isPersonal: true, - } as AccessGroupFactoryContext).create(), - ), - ) - // catch any errors during personal access group creation - .catch(console.error); - - // generate additional groups - const accessGroups = await factory(AccessGroupEntity)({ - allUsers: allUsers, - isPersonal: false, - } as AccessGroupFactoryContext).createMany(this.ACCESS_GROUP_COUNT); - - // Generate Projects, Missions, Files, and Topics... - const projects = - (await factory(ProjectEntity)({ - allAccessGroups: accessGroups, - allUsers: allUsers, - tagTypes, - } as ProjectContext) - .createMany(this.PROJECT_COUNT) - // catch any errors during project creation - .catch(console.error)) ?? []; - - // set access rights for projects - await factory(ProjectAccessEntity)({ - projects: projects, - accessGroups: accessGroups, - }).createMany(this.GROUP_ACCESS_COUNT); - - for (const project of projects) { - const missions = - (await factory(MissionEntity)({ - user: project.creator, - project, - }) - .createMany( - extendedFaker.number.int({ - min: 0, - max: 20, - }), - ) - // catch any errors during mission creation - .catch(console.error)) ?? []; - - for (const mission of missions) { - const files = - (await factory(FileEntity)({ - user: mission.creator, - mission, - }) - .createMany( - extendedFaker.number.int({ - min: 0, - max: 20, - }), - ) - // catch any errors during file creation - .catch(console.error)) ?? []; - - for (const file of files) { - if (file.type === FileType.BAG) continue; // only MCAP files have topics - await factory(TopicEntity)({ file }) - .createMany( - extendedFaker.number.int({ min: 0, max: 20 }), - ) - // catch any errors during topic creation - .catch(console.error); - } - } - } - } -} diff --git a/common/tsconfig.build.json b/common/tsconfig.build.json deleted file mode 100644 index 70e963ece..000000000 --- a/common/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["node_modules"] -} diff --git a/common/tsconfig.json b/common/tsconfig.json deleted file mode 100644 index 163ecac14..000000000 --- a/common/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "strict": true, - "exactOptionalPropertyTypes": true, - "noPropertyAccessFromIndexSignature": true, - "strictPropertyInitialization": true, - "noUncheckedIndexedAccess": true, - "skipLibCheck": true, - "declaration": true, - "esModuleInterop": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "ES2021", - "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", - "incremental": true, - "strictNullChecks": true, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false, - "paths": { - "@common/*": ["*"] - } - } -} diff --git a/common/viewEntities/mission-access-view.entity.ts b/common/viewEntities/mission-access-view.entity.ts deleted file mode 100644 index 45051c863..000000000 --- a/common/viewEntities/mission-access-view.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { DataSource } from 'typeorm'; -import { ViewColumn, ViewEntity } from 'typeorm'; -import MissionEntity from '../entities/mission/mission.entity'; -import { AccessGroupRights } from '../frontend_shared/enum'; - -@ViewEntity({ - expression: (datasource: DataSource) => - datasource - .createQueryBuilder(MissionEntity, 'mission') - .innerJoin('mission.mission_accesses', 'missionAccesses') - .innerJoin('missionAccesses.accessGroup', 'accessGroup') - .innerJoin('accessGroup.memberships', 'memberships') - .innerJoin('memberships.user', 'user') - .select([ - 'mission.uuid as missionUUID', - 'user.uuid as userUUID', - // Aggregate to find the highest level of rights - 'MAX(missionAccesses.rights) as rights', - ]) - // Group by user and mission to get one row per pair - .groupBy('mission.uuid') - .addGroupBy('user.uuid'), -}) -export class MissionAccessViewEntity { - @ViewColumn({ name: 'missionuuid' }) - missionUuid!: string; - - @ViewColumn({ name: 'useruuid' }) - userUuid!: string; - - /** The highest level of access rights the user has for the mission. */ - @ViewColumn({ name: 'rights' }) - rights!: AccessGroupRights; -} diff --git a/common/viewEntities/project-access-view.entity.ts b/common/viewEntities/project-access-view.entity.ts deleted file mode 100644 index e6ffc2129..000000000 --- a/common/viewEntities/project-access-view.entity.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { DataSource, SelectQueryBuilder } from 'typeorm'; -import { ViewColumn, ViewEntity } from 'typeorm'; -import ProjectEntity from '../entities/project/project.entity'; -import { AccessGroupRights } from '../frontend_shared/enum'; - -@ViewEntity({ - expression: (datasource: DataSource): SelectQueryBuilder => - datasource - .createQueryBuilder(ProjectEntity, 'project') - .innerJoin('project.project_accesses', 'projectAccesses') - .innerJoin('projectAccesses.accessGroup', 'accessGroup') - .innerJoin( - 'accessGroup.memberships', - 'memberships', - 'memberships.expirationDate IS NULL OR memberships.expirationDate > NOW()', - ) - .innerJoin('memberships.user', 'user') - .select([ - 'project.uuid as projectUUID', - 'user.uuid as userUUID', - // Use MAX() to find the highest right for this user/project pair - 'MAX(projectAccesses.rights) as rights', - ]) - // Group by user and project to get one row per pair - .groupBy('project.uuid') - .addGroupBy('user.uuid'), -}) -export class ProjectAccessViewEntity { - @ViewColumn({ name: 'projectuuid' }) - projectUuid!: string; - - @ViewColumn({ name: 'useruuid' }) - userUuid!: string; - - /** The highest level of access rights the user has for the project. */ - @ViewColumn() - rights!: AccessGroupRights; -} diff --git a/common/yarn.lock b/common/yarn.lock deleted file mode 100644 index 77e471d3e..000000000 --- a/common/yarn.lock +++ /dev/null @@ -1,2024 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@borewit/text-codec@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@borewit/text-codec/-/text-codec-0.1.1.tgz#7e7f27092473d5eabcffef693a849f2cc48431da" - integrity sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@faker-js/faker@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-10.1.0.tgz#eb72869d01ccbff41a77aa7ac851ce1ac9371129" - integrity sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg== - -"@foxglove/crc@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@foxglove/crc/-/crc-0.0.3.tgz#04cd8816454e14f1ec48de17c949199b4b3ec9c2" - integrity sha512-DjIZsnL3CyP/yQ/vUYA9cjrD0a/8YXejI5ZmsaOiT16cLfZcTwaCxIN01/ys4jsy+dZCQ/9DnWFn7AEFbiMDaA== - -"@ioredis/commands@^1.1.1": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" - integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@lukeed/csprng@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" - integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== - -"@mcap/core@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@mcap/core/-/core-2.1.7.tgz#b2b892c2969a6e9d92d20aa9d103a20cf8215589" - integrity sha512-lp+ZfJ/wKCyB9+vzc5yWe9CfnKa7Vh4nN99yazEjp9eC5DG6ACvGaNWQfQsrOC+3QraAWIY912pCwBWrY31QLQ== - dependencies: - "@foxglove/crc" "^0.0.3" - heap-js "^2.2.0" - tslib "^2.5.0" - -"@microsoft/tsdoc@0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz#2249090633e04063176863a050c8f0808d2b6d2b" - integrity sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA== - -"@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz#9edec61b22c3082018a79f6d1c30289ddf3d9d11" - integrity sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw== - -"@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz#33677a275204898ad8acbf62734fc4dc0b6a4855" - integrity sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw== - -"@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz#19edf7cdc2e7063ee328403c1d895a86dd28f4bb" - integrity sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg== - -"@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz#94fb0543ba2e28766c3fc439cabbe0440ae70159" - integrity sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw== - -"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz#4a0609ab5fe44d07c9c60a11e4484d3c38bbd6e3" - integrity sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg== - -"@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz#0aa5502d547b57abfc4ac492de68e2006e417242" - integrity sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ== - -"@nestjs/common@^10.4.20": - version "10.4.20" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.20.tgz#db021ccfcae398c1cd6c8bc808a169285978d687" - integrity sha512-hxJxZF7jcKGuUzM9EYbuES80Z/36piJbiqmPy86mk8qOn5gglFebBTvcx7PWVbRNSb4gngASYnefBj/Y2HAzpQ== - dependencies: - uid "2.0.2" - file-type "20.4.1" - iterare "1.2.1" - tslib "2.8.1" - -"@nestjs/core@^11.1.9": - version "11.1.9" - resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-11.1.9.tgz#7a77ded789709e194c4f118e3d9bf5182136b076" - integrity sha512-a00B0BM4X+9z+t3UxJqIZlemIwCQdYoPKrMcM+ky4z3pkqqG1eTWexjs+YXpGObnLnjtMPVKWlcZHp3adDYvUw== - dependencies: - uid "2.0.2" - "@nuxt/opencollective" "0.4.1" - fast-safe-stringify "2.1.1" - iterare "1.2.1" - path-to-regexp "8.3.0" - tslib "2.8.1" - -"@nestjs/mapped-types@2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19" - integrity sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw== - -"@nestjs/schedule@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@nestjs/schedule/-/schedule-6.0.1.tgz#048b34e5c16d2d9fa14a9df2bfa2d3b7fa8ef168" - integrity sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q== - dependencies: - cron "4.3.3" - -"@nestjs/swagger@^11.2.2": - version "11.2.2" - resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-11.2.2.tgz#3d5f4cb3464cee5f2e370c24a22a275a49ce9d55" - integrity sha512-i16GRaZ7vlTHIqk8C1UvV/WwQYbWwQymocTvU8mr6QIUBZ6fJc1uGEsw0Mu/JWC0kaV3nbsTj1hZbXrc5Ui4NA== - dependencies: - "@microsoft/tsdoc" "0.16.0" - "@nestjs/mapped-types" "2.1.0" - js-yaml "4.1.1" - lodash "4.17.21" - path-to-regexp "8.3.0" - swagger-ui-dist "5.30.2" - -"@nestjs/typeorm@^10.0.2": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@nestjs/typeorm/-/typeorm-10.0.2.tgz#25e3ec3c9a127b085c06fd7ea25f8690dba145c2" - integrity sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ== - dependencies: - uuid "9.0.1" - -"@nuxt/opencollective@0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@nuxt/opencollective/-/opencollective-0.4.1.tgz#57bc41d2b03b2fba20b935c15950ac0f4bd2cea2" - integrity sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ== - dependencies: - consola "^3.2.3" - -"@opentelemetry/api@^1.4.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" - integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@scarf/scarf@=1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" - integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== - -"@sqltools/formatter@^1.2.5": - version "1.2.5" - resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" - integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== - -"@tokenizer/inflate@^0.2.6": - version "0.2.7" - resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.2.7.tgz#32dd9dfc9abe457c89b3d9b760fc0690c85a103b" - integrity sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg== - dependencies: - debug "^4.4.0" - fflate "^0.8.2" - token-types "^6.0.0" - -"@tokenizer/token@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" - integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/luxon@~3.7.0": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.7.1.tgz#ef51b960ff86801e4e2de80c68813a96e529d531" - integrity sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg== - -"@types/node@^24.10.1": - version "24.10.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.1.tgz#91e92182c93db8bd6224fca031e2370cef9a8f01" - integrity sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ== - dependencies: - undici-types "~7.16.0" - -"@types/validator@^13.11.8": - version "13.12.2" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.2.tgz#760329e756e18a4aab82fc502b51ebdfebbe49f5" - integrity sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA== - -"@willsoto/nestjs-prometheus@^6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@willsoto/nestjs-prometheus/-/nestjs-prometheus-6.0.2.tgz#d603764a923848442ed092411716c0bf211de01f" - integrity sha512-ePyLZYdIrOOdlOWovzzMisIgviXqhPVzFpSMKNNhn6xajhRHeBsjAzSdpxZTc6pnjR9hw1lNAHyKnKl7lAPaVg== - -"@zxing/text-encoding@0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" - integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== - -acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.4.1: - version "8.14.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" - integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" - integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.3" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" - integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== - -ansis@^3.17.0: - version "3.17.0" - resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7" - integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg== - -app-root-path@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" - integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -async@^3.2.4: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -axios@^1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687" - integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.4" - proxy-from-env "^1.1.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bintrees@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" - integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== - -block-stream2@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/block-stream2/-/block-stream2-2.1.0.tgz#ac0c5ef4298b3857796e05be8ebed72196fa054b" - integrity sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg== - dependencies: - readable-stream "^3.4.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -browser-or-node@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-2.1.1.tgz#738790b3a86a8fc020193fa581273fbe65eaea0f" - integrity sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg== - -buffer-crc32@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" - integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== - -buffer-equal-constant-time@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== - -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -bull@^4.16.4: - version "4.16.5" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.16.5.tgz#855a17e7880862a231dac3c8a6392c77668ce49d" - integrity sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ== - dependencies: - cron-parser "^4.9.0" - get-port "^5.1.1" - ioredis "^5.3.2" - lodash "^4.17.21" - msgpackr "^1.11.2" - semver "^7.5.2" - uuid "^8.3.0" - -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" - integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-bound@^1.0.2, call-bound@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681" - integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA== - dependencies: - call-bind-apply-helpers "^1.0.1" - get-intrinsic "^1.2.6" - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -class-transformer@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" - integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== - -class-validator@^0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.2.tgz#a3de95edd26b703e89c151a2023d3c115030340d" - integrity sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw== - dependencies: - "@types/validator" "^13.11.8" - libphonenumber-js "^1.11.1" - validator "^13.9.0" - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.2.0: - version "2.9.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" - integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -cluster-key-slot@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" - integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -consola@^3.2.3: - version "3.4.2" - resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7" - integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cron-parser@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5" - integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q== - dependencies: - luxon "^3.2.1" - -cron@4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/cron/-/cron-4.3.3.tgz#d37cfcbc73ba34a50d9d9ce9b653ae60837377d7" - integrity sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw== - dependencies: - "@types/luxon" "~3.7.0" - luxon "~3.7.0" - -cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -dayjs@^1.11.13: - version "1.11.19" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.19.tgz#15dc98e854bb43917f12021806af897c58ae2938" - integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw== - -debug@^4.3.4: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - -debug@^4.4.0: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decode-uri-component@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -dedent@^1.6.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" - integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== - -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -denque@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" - integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== - -detect-libc@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" - integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dotenv@^16.4.7: - version "16.6.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" - integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -escalade@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - -faker@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" - integrity sha512-ILKg69P6y/D8/wSmDXw35Ly0re8QzQ8pMfBCflsGiZG2ZjMUNLYNexA6lz5pkmJlepVdsiDFUxYAzPQ9/+iGLA== - -fast-safe-stringify@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - -fast-xml-parser@^4.4.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.1.tgz#a7e665ff79b7919100a5202f23984b6150f9b31e" - integrity sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w== - dependencies: - strnum "^1.0.5" - -fflate@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" - integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== - -file-type@20.4.1: - version "20.4.1" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-20.4.1.tgz#8a58cf0922c6098af0ca5d84d5cf859c0c0f56a5" - integrity sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ== - dependencies: - "@tokenizer/inflate" "^0.2.6" - strtok3 "^10.2.0" - token-types "^6.0.0" - uint8array-extras "^1.4.0" - -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -follow-redirects@^1.15.6: - version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -foreground-child@^3.1.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" - integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== - dependencies: - cross-spawn "^7.0.6" - signal-exit "^4.0.1" - -form-data@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" - integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.4, get-intrinsic@^1.2.6: - version "1.2.7" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044" - integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - function-bind "^1.1.2" - get-proto "^1.0.0" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-port@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" - integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== - -get-proto@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -glob@7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^10.4.5: - version "10.5.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" - integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -heap-js@^2.2.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/heap-js/-/heap-js-2.7.1.tgz#3f152279e42214725cac405257554b000b178cc4" - integrity sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA== - -ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ioredis@^5.3.2: - version "5.4.2" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.2.tgz#ebb6f1a10b825b2c0fb114763d7e82114a0bee6c" - integrity sha512-0SZXGNGZ+WzISQ67QDyZ2x0+wVxjjUndtD8oSeik/4ajifeiRufed8fCb8QW8VMyi4MXcS+UO1k/0NGhvq1PAg== - dependencies: - "@ioredis/commands" "^1.1.1" - cluster-key-slot "^1.1.0" - debug "^4.3.4" - 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" - -ipaddr.js@^2.0.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== - -is-arguments@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b" - integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA== - dependencies: - call-bound "^1.0.2" - has-tostringtag "^1.0.2" - -is-callable@^1.1.3: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-function@^1.0.7: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" - integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== - dependencies: - call-bound "^1.0.3" - get-proto "^1.0.0" - has-tostringtag "^1.0.2" - safe-regex-test "^1.1.0" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-regex@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" - integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== - dependencies: - call-bound "^1.0.2" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -is-typed-array@^1.1.14, is-typed-array@^1.1.3: - version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" - integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== - dependencies: - which-typed-array "^1.1.16" - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -iterare@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" - integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== - -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -js-yaml@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" - integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== - dependencies: - argparse "^2.0.1" - -jsonwebtoken@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" - integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^7.5.4" - -jwa@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" - integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== - dependencies: - buffer-equal-constant-time "^1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - -libphonenumber-js@^1.11.1: - version "1.12.7" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.12.7.tgz#e4372cdee413b64bb1600dfde293a7fdc1ba812a" - integrity sha512-0nYZSNj/QEikyhcM5RZFXGlCB/mr4PVamnT1C2sKBnDDTYndrvbybYjvg+PMqAndQHlLbwQ3socolnL3WWTUFA== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== - -lodash.isarguments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== - -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== - -lodash@4.17.21, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - dependencies: - chalk "^2.4.2" - -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -luxon@^3.2.1: - version "3.5.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20" - integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ== - -luxon@~3.7.0: - version "3.7.2" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.7.2.tgz#d697e48f478553cca187a0f8436aff468e3ba0ba" - integrity sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew== - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@^2.1.35: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minio@8.0.6: - version "8.0.6" - resolved "https://registry.yarnpkg.com/minio/-/minio-8.0.6.tgz#ec736e3e3ead1f450af5b9b28256c28e52a83dd0" - integrity sha512-sOeh2/b/XprRmEtYsnNRFtOqNRTPDvYtMWh+spWlfsuCV/+IdxNeKVUMKLqI7b5Dr07ZqCPuaRGU/rB9pZYVdQ== - dependencies: - async "^3.2.4" - block-stream2 "^2.1.0" - browser-or-node "^2.1.1" - buffer-crc32 "^1.0.0" - eventemitter3 "^5.0.1" - fast-xml-parser "^4.4.1" - ipaddr.js "^2.0.1" - lodash "^4.17.21" - mime-types "^2.1.35" - query-string "^7.1.3" - stream-json "^1.8.0" - through2 "^4.0.2" - web-encoding "^1.1.5" - xml2js "^0.5.0 || ^0.6.2" - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -msgpackr-extract@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz#e9d87023de39ce714872f9e9504e3c1996d61012" - integrity sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA== - dependencies: - 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" - -msgpackr@^1.11.2: - version "1.11.2" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.11.2.tgz#4463b7f7d68f2e24865c395664973562ad24473d" - integrity sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g== - optionalDependencies: - msgpackr-extract "^3.0.2" - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -node-gyp-build-optional-packages@5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz#522f50c2d53134d7f3a76cd7255de4ab6c96a3a4" - integrity sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw== - dependencies: - detect-libc "^2.0.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -ora@4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.3.tgz#752a1b7b4be4825546a7a3d59256fa523b6b6d05" - integrity sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg== - dependencies: - chalk "^3.0.0" - cli-cursor "^3.1.0" - cli-spinners "^2.2.0" - is-interactive "^1.0.0" - log-symbols "^3.0.0" - mute-stream "0.0.8" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-to-regexp@8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" - integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== - -pg-cloudflare@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz#a1f3d226bab2c45ae75ea54d65ec05ac6cfafbef" - integrity sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg== - -pg-connection-string@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.9.1.tgz#bb1fd0011e2eb76ac17360dc8fa183b2d3465238" - integrity sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-pool@^3.10.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.1.tgz#481047c720be2d624792100cac1816f8850d31b2" - integrity sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg== - -pg-protocol@^1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.10.3.tgz#ac9e4778ad3f84d0c5670583bab976ea0a34f69f" - integrity sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ== - -pg-types@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@^8.16.3: - version "8.16.3" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.3.tgz#160741d0b44fdf64680e45374b06d632e86c99fd" - integrity sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw== - dependencies: - pg-connection-string "^2.9.1" - pg-pool "^3.10.1" - pg-protocol "^1.10.3" - pg-types "2.2.0" - pgpass "1.0.5" - optionalDependencies: - pg-cloudflare "^1.2.7" - -pgpass@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" - integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== - dependencies: - split2 "^4.1.0" - -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" - integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== - -postgres-date@~1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" - integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - -prom-client@^15.1.3: - version "15.1.3" - resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2" - integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g== - dependencies: - "@opentelemetry/api" "^1.4.0" - tdigest "^0.1.1" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -query-string@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" - integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== - dependencies: - decode-uri-component "^0.2.2" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -readable-stream@3, readable-stream@^3.4.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== - -redis-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== - dependencies: - redis-errors "^1.0.0" - -reflect-metadata@0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -rxjs@^7.8.2: - version "7.8.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" - integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== - dependencies: - tslib "^2.1.0" - -safe-buffer@^5.0.1, safe-buffer@^5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex-test@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" - integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - is-regex "^1.2.1" - -sax@>=0.6.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" - integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== - -semver@^7.5.2: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -semver@^7.5.4: - version "7.7.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" - integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -sha.js@^2.4.12: - version "2.4.12" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f" - integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w== - dependencies: - inherits "^2.0.4" - safe-buffer "^5.2.1" - to-buffer "^1.2.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -split2@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" - integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== - -sql-highlight@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/sql-highlight/-/sql-highlight-6.1.0.tgz#e34024b4c6eac2744648771edfe3c1f894153743" - integrity sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA== - -standard-as-callback@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" - integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - -stream-chain@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09" - integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== - -stream-json@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.9.1.tgz#e3fec03e984a503718946c170db7d74556c2a187" - integrity sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw== - dependencies: - stream-chain "^2.2.5" - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" - integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== - dependencies: - ansi-regex "^6.0.1" - -strnum@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" - integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== - -strtok3@^10.2.0: - version "10.3.4" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-10.3.4.tgz#793ebd0d59df276a085586134b73a406e60be9c1" - integrity sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg== - dependencies: - "@tokenizer/token" "^0.3.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -swagger-ui-dist@5.30.2: - version "5.30.2" - resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.30.2.tgz#b146c5bd92cc712340f8847b546ea64d785efeb2" - integrity sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA== - dependencies: - "@scarf/scarf" "=1.4.0" - -tdigest@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" - integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== - dependencies: - bintrees "1.0.2" - -through2@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" - integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== - dependencies: - readable-stream "3" - -to-buffer@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.2.tgz#ffe59ef7522ada0a2d1cb5dfe03bb8abc3cdc133" - integrity sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw== - dependencies: - isarray "^2.0.5" - safe-buffer "^5.2.1" - typed-array-buffer "^1.0.3" - -token-types@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.1.1.tgz#85bd0ada82939b9178ecd5285881a538c4c00fdd" - integrity sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ== - dependencies: - "@borewit/text-codec" "^0.1.0" - "@tokenizer/token" "^0.3.0" - ieee754 "^1.2.1" - -ts-node@^10.9.2: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -tslib@2.8.1, tslib@^2.1.0, tslib@^2.5.0, tslib@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -typed-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" - integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-typed-array "^1.1.14" - -typeorm-seeding@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/typeorm-seeding/-/typeorm-seeding-1.6.1.tgz#4fe3a1aec9a611007d1135419cde286cced8defd" - integrity sha512-xJIW1pp72hv6npPqbQ7xDvawcDmS60EDUjK++UCfiqT0WE4xTzCn+QK1ZijLkD3GYCqFPuFt4nmeyRJn6VO2Vw== - dependencies: - chalk "^4.0.0" - faker "4.1.0" - glob "7.1.6" - ora "4.0.3" - reflect-metadata "0.1.13" - yargs "15.3.1" - -typeorm@^0.3.27: - version "0.3.27" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.27.tgz#f1e8f3cdc820225f168e901e7e1eaca3a3ec6f3c" - integrity sha512-pNV1bn+1n8qEe8tUNsNdD8ejuPcMAg47u2lUGnbsajiNUr3p2Js1XLKQjBMH0yMRMDfdX8T+fIRejFmIwy9x4A== - dependencies: - "@sqltools/formatter" "^1.2.5" - ansis "^3.17.0" - app-root-path "^3.1.0" - buffer "^6.0.3" - dayjs "^1.11.13" - debug "^4.4.0" - dedent "^1.6.0" - dotenv "^16.4.7" - glob "^10.4.5" - sha.js "^2.4.12" - sql-highlight "^6.0.0" - tslib "^2.8.1" - uuid "^11.1.0" - yargs "^17.7.2" - -typescript@^5.9.3: - version "5.9.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" - integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== - -uid@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" - integrity sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g== - dependencies: - "@lukeed/csprng" "^1.0.0" - -uint8array-extras@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz#10d2a85213de3ada304fea1c454f635c73839e86" - integrity sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A== - -undici-types@~7.16.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" - integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util@^0.12.3: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - -uuid@9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" - integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== - -uuid@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" - integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== - -uuid@^8.3.0: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -validator@^13.9.0: - version "13.12.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" - integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -web-encoding@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864" - integrity sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA== - dependencies: - util "^0.12.3" - optionalDependencies: - "@zxing/text-encoding" "0.9.0" - -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - -which-typed-array@^1.1.16, which-typed-array@^1.1.2: - version "1.1.18" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.18.tgz#df2389ebf3fbb246a71390e90730a9edb6ce17ad" - integrity sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - call-bound "^1.0.3" - for-each "^0.3.3" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -"xml2js@^0.5.0 || ^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" - integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@^18.1.1: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@15.3.1: - version "15.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" - integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.1" - -yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/dev-oauth/Dockerfile b/dev-oauth/Dockerfile index 2da1ef062..e3de89126 100644 --- a/dev-oauth/Dockerfile +++ b/dev-oauth/Dockerfile @@ -14,4 +14,4 @@ COPY . . EXPOSE 5000 # Command to run the Flask app -CMD ["python", "fake_oauth.py"] \ No newline at end of file +CMD ["python", "fake_oauth.py"] diff --git a/dev-oauth/fake_oauth.py b/dev-oauth/fake_oauth.py index 28a681ec3..6f550e35d 100644 --- a/dev-oauth/fake_oauth.py +++ b/dev-oauth/fake_oauth.py @@ -6,17 +6,17 @@ FAKE_USERS = [ { "id": "1", - "email": "admin@kleinkram.leggedrobotics.com", + "email": "admin@kleinkram.dev", "displayName": "Kleinkram Admin User Nr. 1", "photo": "https://randomuser.me/api/portraits/men/8.jpg", "comment": "This user has admin access to Kleinkram. It sees all seeded projects.", }, { "id": "2", - "email": "internal-user@kleinkram.leggedrobotics.com", + "email": "internal-user@kleinkram.dev", "displayName": "Kleinkram User Nr. 2", "photo": "https://randomuser.me/api/portraits/women/71.jpg", - "comment": "This user is an internal user of Kleinkram. She is part of an affiliation group and can create projects. This user sees no seeded projects.", + "comment": "This user is an internal user of Kleinkram. She is part of an affiliation group and can create projects. This user sees the seeded projects.", }, { "id": "3", @@ -43,11 +43,26 @@ def authorize(): client_id = request.args.get("client_id") redirect_uri = request.args.get("redirect_uri") state = request.args.get("state") # Get the state parameter + user_id = request.args.get("user") # Get the user parameter for auto-login - # Pass the state parameter to the template - return render_template( - "login.html", users=FAKE_USERS, redirect_uri=redirect_uri, state=state - ) + # If user parameter is provided, auto-login without showing UI + if user_id: + user = next((u for u in FAKE_USERS if u["id"] == user_id), None) + if not user: + return f"User with ID {user_id} not found. Available user IDs: 1, 2, 3", 400 + + # Generate auth code and redirect immediately + fake_auth_code = f"fake-auth-code-{user['id']}" + print(f"Auto-login: Generated fake auth code: {fake_auth_code} for user: {user['email']}") + + redirect_url = f"{redirect_uri}?code={fake_auth_code}" + if state: + redirect_url += f"&state={state}" + + return redirect(redirect_url) + + # Pass the state parameter to the template for normal flow + return render_template("login.html", users=FAKE_USERS, redirect_uri=redirect_uri, state=state) @app.route("/login", methods=["POST"]) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 738e639a1..391577760 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,13 +1,18 @@ -version: '3.8' - services: + base: + image: kleinkram-base + build: + context: . + dockerfile: docker/base.Dockerfile + api-server: image: rslethz/kleinkram:api-server-dev - container_name: kleinkram-api-server + container_name: kleinkram-api-server-dev build: context: . - dockerfile: ./backend/prod.Dockerfile + dockerfile: docker/backend.Dockerfile + target: production ports: - 127.0.0.1:${SERVER_PORT}:${SERVER_PORT} @@ -24,13 +29,15 @@ services: environment: DEVBUILD: 'false' + REDIS_HOST: redis depends_on: - database + - redis database: image: postgres:17 - container_name: kleinkram-postgres + container_name: kleinkram-postgres-dev ports: - '5432:5432' @@ -48,26 +55,23 @@ services: minio: image: minio/minio - container_name: kleinkram-minio + container_name: kleinkram-minio-dev volumes: - minio_data:/data + - ./docker/minio-entrypoint.sh:/docker-entrypoint.sh:ro environment: MINIO_ROOT_USER: ${MINIO_USER} MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD} + MINIO_PROMETHEUS_AUTH_TYPE: 'public' + MINIO_DATA_BUCKET_NAME: ${MINIO_DATA_BUCKET_NAME} + MINIO_DB_BUCKET_NAME: ${MINIO_DB_BUCKET_NAME} + MINIO_ARTIFACTS_BUCKET_NAME: ${MINIO_ARTIFACTS_BUCKET_NAME} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} - command: > - mkdir -p /data/${MINIO_DATA_BUCKET_NAME} && - mkdir -p /data/${MINIO_DB_BUCKET_NAME} && - mkdir -p /data/${MINIO_ARTIFACTS_BUCKET_NAME} && - /usr/bin/minio server --console-address ':9001' /data & - sleep 10 && - mc alias set myminio http://127.0.0.1:9000 ${MINIO_USER} ${MINIO_PASSWORD} && - mc ilm rule add myminio/${MINIO_ARTIFACTS_BUCKET_NAME} --expire-days 90 || true && - mc admin user svcacct add --access-key '${MINIO_ACCESS_KEY}' --secret-key '${MINIO_SECRET_KEY}' myminio ${MINIO_USER} || true && - echo 'minio is ready' && - sleep infinity" + entrypoint: ['/docker-entrypoint.sh'] ports: - '127.0.0.1:9000:9000' # file storage @@ -78,13 +82,21 @@ services: frontend: image: rslethz/kleinkram:frontend-dev - container_name: kleinkram-frontend + container_name: kleinkram-frontend-dev build: context: . - dockerfile: ./frontend/Dockerfile + dockerfile: docker/frontend.Dockerfile + target: production args: - - QUASAR_ENDPOINT=${QUASAR_ENDPOINT} + - BACKEND_URL=${BACKEND_URL} + - GIT_BRANCH=${GIT_BRANCH} + - GIT_COMMIT=${GIT_COMMIT} + + volumes: + - ./frontend:/app/frontend + - ./packages:/app/packages + - /app/frontend/node_modules ports: - '8003:80' @@ -94,7 +106,7 @@ services: redis: image: redis:latest - container_name: kleinkram-redis + container_name: kleinkram-redis-dev ports: - '6379:6379' networks: @@ -102,10 +114,11 @@ services: queue-consumer: image: rslethz/kleinkram:queue-consumer-dev - container_name: kleinkram-queue-consumer + container_name: kleinkram-queue-consumer-dev build: context: . - dockerfile: ./queueConsumer/prod.Dockerfile + dockerfile: docker/queue-consumer.Dockerfile + target: production env_file: - .env networks: @@ -116,9 +129,13 @@ services: volumes: # mount docker socket to access docker from within the container - /var/run/docker.sock:/var/run/docker.sock + - ./google-service-account.json:/app/queueConsumer/google-service-account.json:ro + # Add docker group to allow access to docker socket + group_add: + - ${DOCKER_GID:-999} prometheus: image: prom/prometheus - container_name: kleinkram-prometheus + container_name: kleinkram-prometheus-dev volumes: - ./observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - logging-prometheus-storage:/prometheus @@ -136,7 +153,7 @@ services: tempo: user: '0:0' image: grafana/tempo:latest - container_name: kleinkram-tempo + container_name: kleinkram-tempo-dev command: ['-config.file=/etc/tempo/tempo.yml'] volumes: - ./observability/tempo/tempo.yml:/etc/tempo/tempo.yml @@ -150,11 +167,12 @@ services: loki: image: grafana/loki - container_name: kleinkram-loki + container_name: kleinkram-loki-dev ports: - '3100:3100' command: - --config.file=/mnt/config/loki-config.yml + - -log.level=error volumes: - ./observability/loki/loki-config.yml:/mnt/config/loki-config.yml:ro networks: @@ -162,13 +180,13 @@ services: grafana: image: grafana/grafana - container_name: kleinkram-grafana + container_name: kleinkram-grafana-dev volumes: - logging-grafana-storage:/var/lib/grafana - ./observability/grafana/provisioning:/etc/grafana/provisioning environment: - - GF_LOG_LEVEL=debug + - GF_LOG_LEVEL=critical ports: - '9050:3000' # localhost:9050 for accessing grafana networks: @@ -178,14 +196,17 @@ services: depends_on: - api-server image: rslethz/kleinkram:docs-dev - container_name: kleinkram-documentation + container_name: kleinkram-documentation-dev volumes: - swagger-spec:/app/src/docs/ build: - context: docs/. - dockerfile: Dockerfile + context: . + dockerfile: docker/docs.Dockerfile + target: production args: - MODE=staging + ports: + - '4000:80' artifact-uploader: image: rslethz/kleinkram:artifact-uploader-dev diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e8b4257c6..e49b10be9 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,13 +1,20 @@ -version: '3.8' - services: + base: + image: kleinkram-base + build: + context: . + dockerfile: docker/base.Dockerfile + api-server: image: rslethz/kleinkram:api-server-latest - container_name: kleinkram-api-server + container_name: kleinkram-api-server-prod build: context: . - dockerfile: ./backend/prod.Dockerfile + dockerfile: docker/backend.Dockerfile + target: production + additional_contexts: + kleinkram-base: service:base ports: - 127.0.0.1:${SERVER_PORT}:${SERVER_PORT} @@ -21,13 +28,17 @@ services: environment: SEED: 'false' DEVBUILD: 'false' + REDIS_HOST: redis + DB_HOST: postgres + DATA_SOURCE_NAME: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE}?sslmode=disable depends_on: - postgres + - redis postgres: image: postgres:17 - container_name: kleinkram-postgres + container_name: kleinkram-postgres-prod ports: - '5432:5432' @@ -45,26 +56,23 @@ services: minio: image: minio/minio - container_name: kleinkram-minio + container_name: kleinkram-minio-prod volumes: - minio_data:/data + - ./docker/minio-entrypoint.sh:/docker-entrypoint.sh:ro environment: MINIO_ROOT_USER: ${MINIO_USER} MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD} + MINIO_PROMETHEUS_AUTH_TYPE: 'public' + MINIO_DATA_BUCKET_NAME: ${MINIO_DATA_BUCKET_NAME} + MINIO_DB_BUCKET_NAME: ${MINIO_DB_BUCKET_NAME} + MINIO_ARTIFACTS_BUCKET_NAME: ${MINIO_ARTIFACTS_BUCKET_NAME} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} - command: > - -c "mkdir -p /data/${MINIO_DATA_BUCKET_NAME} && - mkdir -p /data/${MINIO_DB_BUCKET_NAME} && - mkdir -p /data/${MINIO_ARTIFACTS_BUCKET_NAME} && - /usr/bin/minio server --console-address ':9001' /data & - sleep 10 && - mc alias set myminio http://minio:9000 ${MINIO_USER} ${MINIO_PASSWORD} && - mc ilm rule add myminio/${MINIO_ARTIFACTS_BUCKET_NAME} --expire-days 90 || true && - mc admin user svcacct add --access-key '${MINIO_ACCESS_KEY}' --secret-key '${MINIO_SECRET_KEY}' myminio ${MINIO_USER} || true && - echo 'minio is ready' && - sleep infinity" + entrypoint: ['/docker-entrypoint.sh'] ports: - '127.0.0.1:9000:9000' # file storage @@ -75,13 +83,18 @@ services: frontend: image: rslethz/kleinkram:frontend-latest - container_name: kleinkram-frontend + container_name: kleinkram-frontend-prod build: context: . - dockerfile: ./frontend/Dockerfile + dockerfile: docker/frontend.Dockerfile + target: production args: - - QUASAR_ENDPOINT=${QUASAR_ENDPOINT} + - BACKEND_URL=${BACKEND_URL} + - GIT_BRANCH=${GIT_BRANCH} + - GIT_COMMIT=${GIT_COMMIT} + additional_contexts: + kleinkram-base: service:base ports: - '8003:80' @@ -91,7 +104,7 @@ services: redis: image: redis:latest - container_name: kleinkram-redis + container_name: kleinkram-redis-prod ports: - '6379:6379' networks: @@ -99,12 +112,23 @@ services: queue-consumer: image: rslethz/kleinkram:queue-consumer-latest - container_name: kleinkram-queue-consumer + container_name: kleinkram-queue-consumer-prod build: context: . - dockerfile: ./queueConsumer/prod.Dockerfile + dockerfile: docker/queue-consumer.Dockerfile + target: production + additional_contexts: + kleinkram-base: service:base + artifact-uploader: service:artifact-uploader + ports: + - '127.0.0.1:8080:8080' env_file: - .env + environment: + DB_HOST: postgres + DATA_SOURCE_NAME: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE}?sslmode=disable + REDIS_HOST: redis + REDIS_PORT: 6379 networks: - privatenet depends_on: @@ -113,10 +137,14 @@ services: volumes: # mount docker socket to access docker from within the container - /var/run/docker.sock:/var/run/docker.sock + - ./google-service-account.json:/app/queueConsumer/google-service-account.json:ro + # Add docker group to allow access to docker socket + group_add: + - ${DOCKER_GID:-999} prometheus: image: prom/prometheus - container_name: kleinkram-prometheus + container_name: kleinkram-prometheus-prod volumes: - ./observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - logging-prometheus-storage:/prometheus @@ -134,7 +162,7 @@ services: tempo: user: '0:0' image: grafana/tempo:latest - container_name: kleinkram-tempo + container_name: kleinkram-tempo-prod command: ['-config.file=/etc/tempo/tempo.yml'] volumes: - ./observability/tempo/tempo.yml:/etc/tempo/tempo.yml @@ -148,11 +176,12 @@ services: loki: image: grafana/loki - container_name: kleinkram-loki + container_name: kleinkram-loki-prod ports: - '3100:3100' command: - --config.file=/mnt/config/loki-config.yml + - -log.level=error volumes: - ./observability/loki/loki-config.yml:/mnt/config/loki-config.yml:ro networks: @@ -160,13 +189,13 @@ services: grafana: image: grafana/grafana - container_name: kleinkram-grafana + container_name: kleinkram-grafana-prod volumes: - logging-grafana-storage:/var/lib/grafana - ./observability/grafana/provisioning:/etc/grafana/provisioning environment: - - GF_LOG_LEVEL=debug + - GF_LOG_LEVEL=critical ports: - '9050:3000' # localhost:9050 for accessing grafana networks: @@ -174,10 +203,13 @@ services: documentation: image: rslethz/kleinkram:docs-latest - container_name: kleinkram-documentation + container_name: kleinkram-documentation-prod build: - context: docs/. - dockerfile: Dockerfile + context: . + dockerfile: docker/docs.Dockerfile + target: production + ports: + - '4000:80' artifact-uploader: image: rslethz/kleinkram:artifact-uploader-latest diff --git a/docker-compose.testing.yml b/docker-compose.testing.yml index 84741e3be..b235c5e56 100644 --- a/docker-compose.testing.yml +++ b/docker-compose.testing.yml @@ -1,7 +1,13 @@ services: + base: + image: kleinkram-base + build: + context: . + dockerfile: docker/base.Dockerfile + action_test_file_hash: image: rslethz/kleinkram_actions:file-hash-latest - container_name: kleinkram-action-test-file-hash + container_name: kleinkram-action-test-file-hash-test build: context: ./ dockerfile: backend/tests/actions/file-hash/Dockerfile @@ -9,17 +15,22 @@ services: api-server: image: rslethz/kleinkram:api-server-latest - container_name: kleinkram-api-server + container_name: kleinkram-api-server-test build: context: . - dockerfile: ./backend/prod.Dockerfile + dockerfile: docker/backend.Dockerfile + target: production + additional_contexts: + kleinkram-base: service:base + action-test-file-hash: service:action_test_file_hash ports: - 127.0.0.1:${SERVER_PORT}:${SERVER_PORT} volumes: - - ./backend/.endpoints:/usr/src/app/backend/.endpoints + - ./backend/.endpoints:/app/backend/.endpoints environment: SEED: 'false' DEVBUILD: 'false' + REDIS_HOST: 'redis' env_file: - .env networks: @@ -30,7 +41,7 @@ services: database: image: postgres:17 - container_name: kleinkram-postgres + container_name: kleinkram-postgres-test ports: - '5432:5432' networks: @@ -39,31 +50,29 @@ services: POSTGRES_DB: ${DB_DATABASE} POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} + command: postgres -c hba_file=/etc/postgresql/pg_hba.conf volumes: - db_data_test:/var/lib/postgresql/data + - ./docker/testing-pg_hba.conf:/etc/postgresql/pg_hba.conf minio: image: minio/minio - container_name: kleinkram-minio + container_name: kleinkram-minio-test volumes: - minio_data_test:/data + - ./docker/minio-entrypoint.sh:/docker-entrypoint.sh:ro environment: MINIO_ROOT_USER: ${MINIO_USER} MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD} - entrypoint: sh - # initialize buckets (see https://github.com/minio/minio/issues/4769#issuecomment-331033735) - # and add service worker access key after startup of the minio server - command: > - -c "mkdir -p /data/${MINIO_DATA_BUCKET_NAME} && - mkdir -p /data/${MINIO_DB_BUCKET_NAME} && - mkdir -p /data/${MINIO_ARTIFACTS_BUCKET_NAME} && - /usr/bin/minio server --console-address ':9001' /data & - sleep 10 && - mc alias set myminio http://minio:9000 ${MINIO_USER} ${MINIO_PASSWORD} && - mc ilm rule add myminio/${MINIO_ARTIFACTS_BUCKET_NAME} --expire-days 90 || true && - mc admin user svcacct add --access-key '${MINIO_ACCESS_KEY}' --secret-key '${MINIO_SECRET_KEY}' myminio ${MINIO_USER} || true && - echo 'minio is ready' && - sleep infinity" + MINIO_PROMETHEUS_AUTH_TYPE: 'public' + MINIO_DATA_BUCKET_NAME: ${MINIO_DATA_BUCKET_NAME} + MINIO_DB_BUCKET_NAME: ${MINIO_DB_BUCKET_NAME} + MINIO_ARTIFACTS_BUCKET_NAME: ${MINIO_ARTIFACTS_BUCKET_NAME} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + + entrypoint: ['/docker-entrypoint.sh'] + ports: - '127.0.0.1:9000:9000' - '127.0.0.1:9001:9001' @@ -73,13 +82,17 @@ services: frontend: image: rslethz/kleinkram:frontend-dev - container_name: kleinkram-frontend + container_name: kleinkram-frontend-test build: context: . - dockerfile: ./frontend/Dockerfile + dockerfile: docker/frontend.Dockerfile args: - - QUASAR_ENDPOINT=${QUASAR_ENDPOINT} + - BACKEND_URL=${BACKEND_URL} + - GIT_BRANCH=${GIT_BRANCH} + - GIT_COMMIT=${GIT_COMMIT} + additional_contexts: + kleinkram-base: service:base ports: - '8003:8003' @@ -91,7 +104,7 @@ services: redis: image: redis:latest - container_name: kleinkram-redis + container_name: kleinkram-redis-test ports: - '6379:6379' networks: @@ -99,68 +112,41 @@ services: queue-consumer: image: rslethz/kleinkram:queue-consumer-latest - container_name: kleinkram-queue-consumer + container_name: kleinkram-queue-consumer-test hostname: 'DO_NOT_CHANGE' build: context: . - dockerfile: queueConsumer/prod.Dockerfile + dockerfile: docker/queue-consumer.Dockerfile + target: production + additional_contexts: + kleinkram-base: service:base + artifact-uploader: service:artifact-uploader volumes: # mount docker socket to access docker from within the container - /var/run/docker.sock:/var/run/docker.sock + - ./google-service-account.json:/app/queueConsumer/google-service-account.json:ro + environment: + REDIS_HOST: 'redis' env_file: - .env networks: - privatenet depends_on: - database - - loki - - prometheus: - image: prom/prometheus - container_name: kleinkram-prometheus - volumes: - - ./observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - - logging-prometheus-storage:/prometheus - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - '--web.enable-remote-write-receiver' - - '--enable-feature=exemplar-storage' - - '--enable-feature=native-histograms' - ports: - - '9090:9090' - networks: - - privatenet - - webnet - tempo: - user: '0:0' - image: grafana/tempo:latest - container_name: kleinkram-tempo - command: ['-config.file=/etc/tempo/tempo.yml'] - volumes: - - ./observability/tempo/tempo.yml:/etc/tempo/tempo.yml - - logging-tempo-data:/tmp/tempo + fake-oauth: + build: dev-oauth ports: - - '3200' # tempo - - '4317' # otlp grpc - - '4318' # otlp http + - '8004:5000' networks: - privatenet - webnet - loki: - image: grafana/loki - container_name: kleinkram-loki - ports: - - '3100:3100' - command: - - --config.file=/mnt/config/loki-config.yml - volumes: - - ./observability/loki/loki-config.yml:/mnt/config/loki-config.yml:ro - networks: - - privatenet - - webnet + artifact-uploader: + image: rslethz/kleinkram:artifact-uploader-latest + build: + context: queueConsumer/src/artifactUpload + dockerfile: Dockerfile volumes: db_data_test: diff --git a/docker-compose.yml b/docker-compose.yml index bb1697c82..4a3ba89c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,25 +1,72 @@ services: + base: + image: kleinkram-base + build: + context: . + dockerfile: docker/base.Dockerfile + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} + api-server: container_name: kleinkram-api-server build: context: . - dockerfile: backend/Dockerfile + dockerfile: docker/backend.Dockerfile + target: development + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} + additional_contexts: + kleinkram-base: service:base develop: watch: - action: sync - path: ./common - target: /usr/src/app/common + path: ./packages + target: /app/packages ignore: - node_modules/ - dist/ - action: sync path: ./backend - target: /usr/src/app/backend + target: /app/backend ignore: - node_modules/ - dist/ + - tests/ + # Backend config files + - action: rebuild + path: ./backend/package.json + - action: rebuild + path: ./backend/tsconfig.json + - action: rebuild + path: ./backend/tsconfig.build.json + - action: rebuild + path: ./backend/nest-cli.json + - action: rebuild + path: ./backend/webpack.config.js + # Shared packages config files + - action: rebuild + path: ./packages/shared/package.json + - action: rebuild + path: ./packages/shared/tsconfig.json + - action: rebuild + path: ./packages/backend-common/package.json + - action: rebuild + path: ./packages/backend-common/tsconfig.json + - action: rebuild + path: ./packages/api-dto/package.json + - action: rebuild + path: ./packages/api-dto/tsconfig.json + - action: rebuild + path: ./packages/validation/package.json + - action: rebuild + path: ./packages/validation/tsconfig.json + # Root lockfile + - action: rebuild + path: ./pnpm-lock.yaml ports: - 127.0.0.1:${SERVER_PORT}:${SERVER_PORT} @@ -33,17 +80,39 @@ services: environment: DEVBUILD: 'true' + MINIO_ENDPOINT_INTERNAL: 'minio' + REDIS_HOST: redis depends_on: - - database - - minio - - redis + database: + condition: service_started + minio: + condition: service_healthy + redis: + condition: service_started + base: + condition: service_started + + volumes: + - ./backend:/app/backend + - backend_node_modules:/app/backend/node_modules + - ./packages:/app/packages + - ./cli:/app/cli + - ./cli/tests/generate_test_data.py:/app/common/cli/tests/generate_test_data.py + # Mask host node_modules with anonymous volumes to avoid broken symlinks + - /app/packages/api-dto/node_modules + - /app/packages/backend-common/node_modules + - /app/packages/shared/node_modules + - /app/packages/validation/node_modules + - ./docs:/app/docs database: image: postgres:17 container_name: kleinkram-postgres + command: postgres -c log_min_messages=ERROR -c log_min_error_statement=ERROR + ports: - '5432:5432' @@ -65,32 +134,31 @@ services: volumes: - minio_data:/data + - ./docker/minio-entrypoint.sh:/docker-entrypoint.sh:ro environment: MINIO_ROOT_USER: ${MINIO_USER} MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD} MINIO_PROMETHEUS_AUTH_TYPE: 'public' + MINIO_DATA_BUCKET_NAME: ${MINIO_DATA_BUCKET_NAME} + MINIO_DB_BUCKET_NAME: ${MINIO_DB_BUCKET_NAME} + MINIO_ARTIFACTS_BUCKET_NAME: ${MINIO_ARTIFACTS_BUCKET_NAME} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} - entrypoint: sh - - # initialize buckets (see https://github.com/minio/minio/issues/4769#issuecomment-331033735) - # and add service worker access key after startup of the minio server - command: > - -c "mkdir -p /data/${MINIO_DATA_BUCKET_NAME} && - mkdir -p /data/${MINIO_DB_BUCKET_NAME} && - mkdir -p /data/${MINIO_ARTIFACTS_BUCKET_NAME} && - /usr/bin/minio server --console-address ':9001' /data & - sleep 10 && - mc alias set myminio http://minio:9000 ${MINIO_USER} ${MINIO_PASSWORD} && - mc ilm rule add myminio/${MINIO_ARTIFACTS_BUCKET_NAME} --expire-days 90 || true && - mc admin user svcacct add --access-key '${MINIO_ACCESS_KEY}' --secret-key '${MINIO_SECRET_KEY}' myminio ${MINIO_USER} || true && - echo 'minio is ready' && - sleep infinity" + entrypoint: ['/docker-entrypoint.sh'] ports: - '127.0.0.1:9000:9000' - '127.0.0.1:9001:9001' + healthcheck: + test: + ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live'] + interval: 30s + timeout: 20s + retries: 3 + networks: - public-network - private-network @@ -100,16 +168,30 @@ services: build: context: . - dockerfile: ./frontend/dev.Dockerfile + dockerfile: docker/frontend.Dockerfile + target: development args: - - QUASAR_ENDPOINT=${QUASAR_ENDPOINT} + - BACKEND_URL=${BACKEND_URL} + - USER_ID=${USER_ID:-1000} + - GROUP_ID=${GROUP_ID:-1000} + additional_contexts: + kleinkram-base: service:base ports: - '8003:8003' + volumes: + - ./frontend:/app/frontend + - ./.git:/app/.git:ro + - frontend_node_modules:/app/frontend/node_modules + - ./packages:/app/packages + env_file: - .env + environment: + BACKEND_URL: ${BACKEND_URL} + develop: watch: - action: sync @@ -119,17 +201,41 @@ services: - node_modules/ - dist/ - action: sync - path: ./common - target: /app/common + path: ./packages + target: /app/packages ignore: - node_modules/ - dist/ - - action: sync - path: ./common/frontend_shared - target: /app/common/frontend_shared + - action: rebuild + path: ./frontend/package.json + - action: rebuild + path: ./frontend/pnpm-lock.yaml + - action: rebuild + path: ./frontend/tsconfig.json + - action: rebuild + path: ./frontend/quasar.config.ts + # Shared packages config files + - action: rebuild + path: ./packages/shared/package.json + - action: rebuild + path: ./packages/shared/tsconfig.json + - action: rebuild + path: ./packages/backend-common/package.json + - action: rebuild + path: ./packages/backend-common/tsconfig.json + - action: rebuild + path: ./packages/api-dto/package.json + - action: rebuild + path: ./packages/api-dto/tsconfig.json + - action: rebuild + path: ./packages/validation/package.json + - action: rebuild + path: ./packages/validation/tsconfig.json + # Root lockfile + - action: rebuild + path: ./pnpm-lock.yaml depends_on: - - api-server - minio networks: @@ -140,6 +246,8 @@ services: container_name: kleinkram-redis + command: redis-server --loglevel warning + ports: - '6379:6379' @@ -151,37 +259,91 @@ services: build: context: . - dockerfile: queueConsumer/Dockerfile + dockerfile: docker/queue-consumer.Dockerfile + target: development + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} + additional_contexts: + kleinkram-base: service:base + artifact-uploader: service:artifact-uploader volumes: + - ./queueConsumer:/app/queueConsumer + - queue_consumer_node_modules:/app/queueConsumer/node_modules + - ./packages:/app/packages # mount docker socket to access docker from within the container - /var/run/docker.sock:/var/run/docker.sock + - ./google-service-account.json:/app/queueConsumer/google-service-account.json:ro + # Mask host node_modules with anonymous volumes to avoid broken symlinks + - /app/packages/api-dto/node_modules + - /app/packages/backend-common/node_modules + - /app/packages/shared/node_modules + - /app/packages/validation/node_modules + + # Add docker group to allow access to docker socket + group_add: + - ${DOCKER_GID:-999} develop: watch: - action: sync path: ./queueConsumer - target: /usr/src/queueConsumer + target: /app/queueConsumer ignore: - node_modules/ - dist/ - - action: sync - path: ./common - target: /usr/src/common + path: ./packages + target: /app/packages ignore: - node_modules/ - dist/ + # QueueConsumer config files + - action: rebuild + path: ./queueConsumer/package.json + - action: rebuild + path: ./queueConsumer/tsconfig.json + - action: rebuild + path: ./queueConsumer/tsconfig.build.json + - action: rebuild + path: ./queueConsumer/nest-cli.json + - action: rebuild + path: ./queueConsumer/webpack.config.js + # Shared packages config files + - action: rebuild + path: ./packages/shared/package.json + - action: rebuild + path: ./packages/shared/tsconfig.json + - action: rebuild + path: ./packages/backend-common/package.json + - action: rebuild + path: ./packages/backend-common/tsconfig.json + - action: rebuild + path: ./packages/api-dto/package.json + - action: rebuild + path: ./packages/api-dto/tsconfig.json + - action: rebuild + path: ./packages/validation/package.json + - action: rebuild + path: ./packages/validation/tsconfig.json + # Root lockfile + - action: rebuild + path: ./pnpm-lock.yaml env_file: - .env + environment: + REDIS_HOST: redis + networks: - private-network depends_on: - database - redis + - base prometheus: image: prom/prometheus @@ -195,8 +357,9 @@ services: - '--web.enable-remote-write-receiver' - '--enable-feature=exemplar-storage' - '--enable-feature=native-histograms' + - '--log.level=error' ports: - - '9090:9090' + - '9099:9090' networks: - private-network - public-network @@ -224,6 +387,7 @@ services: - '3100:3100' command: - --config.file=/mnt/config/loki-config.yml + - -log.level=error volumes: - ./observability/loki/loki-config.yml:/mnt/config/loki-config.yml:ro networks: @@ -256,8 +420,9 @@ services: depends_on: - api-server build: - context: docs/. - dockerfile: local-dev.Dockerfile + context: . + dockerfile: docker/docs.Dockerfile + target: development environment: DEVBUILD: 'true' ports: @@ -267,13 +432,19 @@ services: - vitepress_dist:/app/src/.vitepress/dist - node_modules:/app/src/node_modules - ./backend/docs:/app/src/docs/ + - ./tsconfig.base.json:/app/tsconfig.base.json + - ./packages:/app/packages + + command: sh -c "ln -sf /app/src/scripts /app/scripts && npm run docs:dev /app/src" - artifactUploader: + artifact-uploader: container_name: kleinkram-artifact-uploader - image: rslethz/grandtour-datasets:artifact-uploader-latest + image: rslethz/kleinkram:artifact-uploader-latest build: context: queueConsumer/src/artifactUpload dockerfile: Dockerfile + environment: + KLEINKRAM_ACTION_UUID: 'dev-action-uuid' postgres-exporter: image: prometheuscommunity/postgres-exporter @@ -303,6 +474,9 @@ volumes: logging-tempo-data: vitepress_dist: node_modules: + backend_node_modules: + frontend_node_modules: + queue_consumer_node_modules: networks: public-network: diff --git a/docker/backend.Dockerfile b/docker/backend.Dockerfile new file mode 100644 index 000000000..b18cad60f --- /dev/null +++ b/docker/backend.Dockerfile @@ -0,0 +1,42 @@ +FROM kleinkram-base AS development + +# Install runtime dependencies for Python (rosbags) +USER root +RUN apt-get update && apt-get install -y --no-install-recommends python3-pip && rm -rf /var/lib/apt/lists/* +RUN pip3 install rosbags --break-system-packages --no-cache-dir +USER node + +WORKDIR /app/backend + +CMD ["./entrypoint.sh"] + + +FROM node:22-slim AS build + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY pnpm-lock.yaml ./ +RUN pnpm fetch + +COPY . . +RUN pnpm install -r + +# Build packages and backend +RUN pnpm --filter @kleinkram/shared build +RUN pnpm --filter @kleinkram/validation build +RUN pnpm --filter @kleinkram/api-dto build +RUN pnpm --filter @kleinkram/backend-common build +RUN NODE_ENV=production pnpm --filter kleinkram-backend build +RUN pnpm deploy --filter=kleinkram-backend --prod --legacy /prod/backend + +FROM gcr.io/distroless/nodejs22-debian12 AS production + +WORKDIR /app + +COPY --from=build /app/backend/dist/main.js ./backend/dist/main.js + +WORKDIR /app/backend + +CMD ["dist/main.js"] diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile new file mode 100644 index 000000000..fb486eb86 --- /dev/null +++ b/docker/base.Dockerfile @@ -0,0 +1,35 @@ +FROM node:22-slim AS base + +RUN corepack enable && corepack prepare pnpm@latest --activate +RUN apt-get update && apt-get install -y procps && rm -rf /var/lib/apt/lists/* + + +ARG USER_ID=1000 +ARG GROUP_ID=1000 + +RUN if [ ${USER_ID} -ne 1000 ]; then \ + deluser --remove-home node && \ + addgroup -g ${GROUP_ID} node && \ + adduser -u ${USER_ID} -G node -s /bin/sh -D node; \ + fi + +WORKDIR /app +RUN chown node:node /app +USER node + +# Install dependencies using pnpm fetch for caching +COPY --chown=node:node pnpm-lock.yaml ./ +RUN pnpm fetch + +# Only copy manifest files needed for installation, NOT source code +COPY --chown=node:node package.json pnpm-workspace.yaml tsconfig.base.json ./ +COPY --chown=node:node packages/shared/package.json ./packages/shared/ +COPY --chown=node:node packages/validation/package.json ./packages/validation/ +COPY --chown=node:node packages/api-dto/package.json ./packages/api-dto/ +COPY --chown=node:node packages/backend-common/package.json ./packages/backend-common/ +COPY --chown=node:node backend/package.json ./backend/ +COPY --chown=node:node queueConsumer/package.json ./queueConsumer/ +COPY --chown=node:node frontend/package.json ./frontend/ +COPY --chown=node:node frontend/create_build_info.sh ./frontend/ + +RUN pnpm install -r diff --git a/docker/docs.Dockerfile b/docker/docs.Dockerfile new file mode 100644 index 000000000..6a2584b63 --- /dev/null +++ b/docker/docs.Dockerfile @@ -0,0 +1,128 @@ +FROM node:22-slim AS base + +WORKDIR /app + +# Copy package.json from docs folder +COPY docs/package.json docs/pnpm-lock.yaml tsconfig.base.json ./ + +RUN corepack enable && corepack prepare pnpm@latest --activate +RUN pnpm install --frozen-lockfile + +# Development Stage +FROM base AS development +CMD ["npm", "run", "docs:dev", "/app/src"] + +# Swagger Stage +FROM node:22-slim AS swagger-builder +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@latest --activate + +# Copy root configs +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json tsconfig.json ./ + +# Copy backend and packages manifests to install dependencies efficiently +COPY backend/package.json ./backend/ +COPY packages/api-dto/package.json ./packages/api-dto/ +COPY packages/backend-common/package.json ./packages/backend-common/ +COPY packages/shared/package.json ./packages/shared/ +COPY packages/validation/package.json ./packages/validation/ + +# Install dependencies (including devDependencies for ts-node) +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY backend ./backend +COPY packages ./packages + +# Generate Swagger +WORKDIR /app/backend +RUN SERVER_PORT=3000 \ + DB_HOST=localhost \ + DB_PORT=5432 \ + DB_USER=test \ + DB_PASSWORD=test \ + DB_DATABASE=test \ + DEV=false \ + ENTITIES="src/**/*.entity.ts" \ + REDIS_HOST=localhost \ + JWT_SECRET=secret \ + MINIO_ACCESS_KEY=test \ + MINIO_SECRET_KEY=test \ + MINIO_DATA_BUCKET_NAME=test \ + MINIO_DB_BUCKET_NAME=test \ + MINIO_ARTIFACTS_BUCKET_NAME=test \ + MINIO_ENDPOINT=localhost \ + MINIO_USER=test \ + MINIO_PASSWORD=test \ + FRONTEND_URL=http://localhost:8000 \ + BACKEND_URL=http://localhost:3000 \ + DOCS_URL=http://localhost:4000 \ + GOOGLE_KEY_FILE=test.json \ + ARTIFACTS_UPLOADER_IMAGE=test \ + VITE_USE_FAKE_OAUTH_FOR_DEVELOPMENT=true \ + node -r ts-node/register -r tsconfig-paths/register ./scripts/generate-openapi.ts + +# Builder Stage +FROM base AS builder +ARG MODE="production" + +# Copy all docs source +COPY docs ./src +RUN mkdir -p ./src/development/api/generated + +# Copy generated markdown files +COPY --from=swagger-builder /app/backend/*.md ./src/development/api/generated/ + +# build postgres entity documentation +COPY packages/backend-common/src ./packages/backend-common/src + +# Run the locally installed tsx binary directly to avoid pnpm exec lookup issues +RUN mkdir -p src/development/application-structure && \ + cd src && \ + ../node_modules/.bin/tsx scripts/generate-entity-documentation.ts && \ + test -f development/application-structure/postgres.md + +# Build the docs +RUN pnpm run docs:build:${MODE} -- src + +FROM debian:bookworm-slim AS nginx-base +RUN apt-get update && apt-get install -y nginx +RUN rm -rf /var/lib/apt/lists/* +RUN rm -rf /etc/nginx/sites-enabled/default + +# Configure nginx for non-root execution +RUN sed -i 's/user www-data;//g' /etc/nginx/nginx.conf +RUN sed -i 's/pid \/run\/nginx.pid;/pid \/tmp\/nginx.pid;/g' /etc/nginx/nginx.conf +RUN ln -sf /dev/stdout /var/log/nginx/access.log +RUN ln -sf /dev/stderr /var/log/nginx/error.log +RUN mkdir -p /var/lib/nginx/body /var/lib/nginx/fastcgi /var/lib/nginx/proxy /var/lib/nginx/scgi /var/lib/nginx/uwsgi +RUN chmod -R 777 /var/lib/nginx /var/log/nginx + +# Production Stage +FROM gcr.io/distroless/base-debian12 AS production + +# Copy nginx binary and config +COPY --from=nginx-base /usr/sbin/nginx /usr/sbin/nginx +COPY --from=nginx-base /etc/nginx /etc/nginx +COPY --from=nginx-base /var/lib/nginx /var/lib/nginx +COPY --from=nginx-base /var/log/nginx /var/log/nginx + +# Copy required shared libraries (identified via ldd on debian:bookworm-slim) +COPY --from=nginx-base /lib/x86_64-linux-gnu/libcrypt.so.1 /lib/x86_64-linux-gnu/libcrypt.so.1 +COPY --from=nginx-base /lib/x86_64-linux-gnu/libpcre2-8.so.0 /lib/x86_64-linux-gnu/libpcre2-8.so.0 +COPY --from=nginx-base /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1 + +# Copy the built docs +COPY --from=builder /app/src/.vitepress/dist /usr/share/nginx/html +COPY --from=swagger-builder /app/backend/swagger.json /usr/share/nginx/html/swagger.json +# Copy generated markdown files +COPY --from=swagger-builder /app/backend/*.md /usr/share/nginx/html/development/api/generated/ +COPY --from=swagger-builder /app/backend/api-modules.json /usr/share/nginx/html/development/api/generated/api-modules.json + +# Copy the nginx config +COPY docs/nginx.conf /etc/nginx/conf.d/default.conf + +USER 65532:65532 + +# Start the nginx server +ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"] diff --git a/docker/frontend.Dockerfile b/docker/frontend.Dockerfile new file mode 100644 index 000000000..0efad3e1a --- /dev/null +++ b/docker/frontend.Dockerfile @@ -0,0 +1,76 @@ +FROM kleinkram-base AS development + +WORKDIR /app/frontend + +CMD ["pnpm", "start:dev"] + + +FROM node:22-slim AS build + + +RUN corepack enable && corepack prepare pnpm@latest --activate + +ARG BACKEND_URL +ENV BACKEND_URL=$BACKEND_URL + +ARG GIT_BRANCH +ENV GIT_BRANCH=$GIT_BRANCH + +ARG GIT_COMMIT +ENV GIT_COMMIT=$GIT_COMMIT + + +WORKDIR /app + +COPY pnpm-lock.yaml ./ +RUN pnpm fetch + +COPY . . +RUN pnpm install -r + +# Generate build info explicitly to ensure it captures build-time ARGs +WORKDIR /app/frontend +RUN sh ./create_build_info.sh +WORKDIR /app + +RUN pnpm --filter @kleinkram/shared build +RUN pnpm --filter @kleinkram/validation build +RUN pnpm --filter @kleinkram/api-dto build +RUN pnpm --filter kleinkram-frontend build + +FROM debian:bookworm-slim AS nginx-base +RUN apt-get update && \ + apt-get install -y nginx && \ + rm -rf /var/lib/apt/lists/* && \ + rm -rf /etc/nginx/sites-enabled/default + +# Configure nginx for non-root execution +RUN sed -i 's/user www-data;//g' /etc/nginx/nginx.conf && \ + sed -i 's/pid \/run\/nginx.pid;/pid \/tmp\/nginx.pid;/g' /etc/nginx/nginx.conf && \ + # Forward logs to stdout/stderr + ln -sf /dev/stdout /var/log/nginx/access.log && \ + ln -sf /dev/stderr /var/log/nginx/error.log && \ + # Create temp directory for nginx runtime files corresponding to default config + mkdir -p /var/lib/nginx/body /var/lib/nginx/fastcgi /var/lib/nginx/proxy /var/lib/nginx/scgi /var/lib/nginx/uwsgi && \ + chmod -R 777 /var/lib/nginx /var/log/nginx + + +FROM gcr.io/distroless/base-debian12 AS production + +# Copy nginx binary and config +COPY --from=nginx-base /usr/sbin/nginx /usr/sbin/nginx +COPY --from=nginx-base /etc/nginx /etc/nginx +COPY --from=nginx-base /var/lib/nginx /var/lib/nginx +COPY --from=nginx-base /var/log/nginx /var/log/nginx + +# Copy required shared libraries (identified via ldd on debian:bookworm-slim) +COPY --from=nginx-base /lib/x86_64-linux-gnu/libcrypt.so.1 /lib/x86_64-linux-gnu/libcrypt.so.1 +COPY --from=nginx-base /lib/x86_64-linux-gnu/libpcre2-8.so.0 /lib/x86_64-linux-gnu/libpcre2-8.so.0 +COPY --from=nginx-base /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1 + +COPY --from=build /app/frontend/dist/spa /usr/share/nginx/html +COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf + +USER 65532:65532 + +ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"] diff --git a/docker/minio-entrypoint.sh b/docker/minio-entrypoint.sh new file mode 100755 index 000000000..1b7834f67 --- /dev/null +++ b/docker/minio-entrypoint.sh @@ -0,0 +1,47 @@ +#!/bin/sh +set -e + +# 1. Start MinIO in the background +echo "Starting MinIO server..." +mkdir -p /data +/usr/bin/minio server --console-address ':9001' /data & + +# 2. Wait for the server to be actually ready (Healthcheck Loop) +echo "Waiting for MinIO to startup..." +timeout=30 +while ! curl -s http://127.0.0.1:9000/minio/health/live > /dev/null; do + if [ $timeout -le 0 ]; then echo "MinIO failed to start"; exit 1; fi + echo "MinIO not ready yet, retrying..." + sleep 1 + timeout=$((timeout - 1)) +done + +# 3. Configure the alias (using ROOT credentials) +# We use 127.0.0.1 to avoid Docker DNS resolution issues inside the container +echo "Configuring MinIO alias..." +mc alias set myminio http://127.0.0.1:9000 "${MINIO_ROOT_USER}" "${MINIO_ROOT_PASSWORD}" + +# 4. Create Buckets +echo "Creating buckets..." +mc mb myminio/${MINIO_DATA_BUCKET_NAME} --ignore-existing +mc mb myminio/${MINIO_DB_BUCKET_NAME} --ignore-existing +mc mb myminio/${MINIO_ARTIFACTS_BUCKET_NAME} --ignore-existing + +# 5. Set Lifecycle Rules (ILM) +echo "Setting lifecycle rules..." +mc ilm rule add myminio/${MINIO_ARTIFACTS_BUCKET_NAME} --expire-days 90 || true + +# 6. Create Service Account +# Notes: +# - We attach this to MINIO_ROOT_USER so it inherits Admin/ReadWrite permissions automatically. +# - We removed '--policy readwrite' because that flag expects a JSON file path, not a name. +echo "Creating service account..." +mc admin user svcacct add myminio "${MINIO_ROOT_USER}" \ + --access-key "${MINIO_ACCESS_KEY}" \ + --secret-key "${MINIO_SECRET_KEY}" \ + || true + +echo "MinIO is ready!" + +# 7. Keep the container running +wait diff --git a/docker/queue-consumer.Dockerfile b/docker/queue-consumer.Dockerfile new file mode 100644 index 000000000..48e140816 --- /dev/null +++ b/docker/queue-consumer.Dockerfile @@ -0,0 +1,56 @@ +FROM kleinkram-base AS development + +USER root +RUN apt-get update && apt-get install -y curl && \ + curl -sL "https://github.com/google/go-containerregistry/releases/download/v0.19.1/go-containerregistry_Linux_x86_64.tar.gz" > crane.tar.gz && \ + tar -zxvf crane.tar.gz -C /usr/local/bin/ crane && \ + rm crane.tar.gz && \ + curl -sL "https://github.com/foxglove/mcap/releases/latest/download/mcap-linux-amd64" -o /usr/local/bin/mcap && \ + chmod +x /usr/local/bin/mcap && \ + apt-get clean && rm -rf /var/lib/apt/lists/* +USER node + +WORKDIR /app/queueConsumer + +CMD ["pnpm", "start:dev"] + + +FROM node:22-slim AS build + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY pnpm-lock.yaml ./ +RUN pnpm fetch + +COPY . . +RUN pnpm install -r + +RUN pnpm --filter @kleinkram/shared build +RUN pnpm --filter @kleinkram/validation build +RUN pnpm --filter @kleinkram/api-dto build +RUN pnpm --filter @kleinkram/backend-common build +RUN NODE_ENV=production pnpm --filter kleinkram-queue-consumer build +RUN pnpm deploy --filter=kleinkram-queue-consumer --prod --legacy /prod/queueConsumer + +FROM node:22-slim AS production + +RUN apt-get update && apt-get install -y curl ca-certificates && \ + # Install Crane \ + curl -sL "https://github.com/google/go-containerregistry/releases/download/v0.19.1/go-containerregistry_Linux_x86_64.tar.gz" > crane.tar.gz && \ + tar -zxvf crane.tar.gz -C /usr/local/bin/ crane && \ + rm crane.tar.gz && \ + # Install Mcap \ + curl -sL "https://github.com/foxglove/mcap/releases/latest/download/mcap-linux-amd64" -o /usr/local/bin/mcap && \ + chmod +x /usr/local/bin/mcap && \ + # Cleanup \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=build /app/queueConsumer/dist/main.js ./queueConsumer/dist/main.js + +WORKDIR /app/queueConsumer + +CMD ["node", "dist/main.js"] diff --git a/docker/testing-pg_hba.conf b/docker/testing-pg_hba.conf new file mode 100644 index 000000000..49b159889 --- /dev/null +++ b/docker/testing-pg_hba.conf @@ -0,0 +1,10 @@ +# TYPE DATABASE USER ADDRESS METHOD + +# "local" is for Unix domain socket connections only +local all all trust +# IPv4 local connections: +host all all 127.0.0.1/32 trust +# IPv6 local connections: +host all all ::1/128 trust +# Allow all connections from docker network (and everywhere else for testing convenience/debugging) +host all all 0.0.0.0/0 trust diff --git a/docs/.dockerignore b/docs/.dockerignore index 6bd806f64..f2b32cd9c 100644 --- a/docs/.dockerignore +++ b/docs/.dockerignore @@ -1,3 +1,3 @@ node_modules -.vitepress/dist \ No newline at end of file +.vitepress/dist diff --git a/docs/.gitignore b/docs/.gitignore index 8d5ed97b4..cb52c6706 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,3 +1,3 @@ /node_modules/ /.vitepress/dist/ -/.vitepress/cache \ No newline at end of file +/.vitepress/cache diff --git a/docs/.vitepress/components/AccessWrites/access-badge.css b/docs/.vitepress/components/AccessWrites/access-badge.css index 90aac9f9d..0ad71d182 100644 --- a/docs/.vitepress/components/AccessWrites/access-badge.css +++ b/docs/.vitepress/components/AccessWrites/access-badge.css @@ -7,4 +7,4 @@ font-weight: bold; display: inline-block; text-align: center; -} \ No newline at end of file +} diff --git a/docs/.vitepress/components/AccessWrites/Any.vue b/docs/.vitepress/components/AccessWrites/any.vue similarity index 54% rename from docs/.vitepress/components/AccessWrites/Any.vue rename to docs/.vitepress/components/AccessWrites/any.vue index 26e2ff67f..ad6c1dc8a 100644 --- a/docs/.vitepress/components/AccessWrites/Any.vue +++ b/docs/.vitepress/components/AccessWrites/any.vue @@ -1,3 +1,8 @@ + +// eslint-disable-next-line vue/multi-word-component-names +// eslint-disable-next-line unicorn/filename-case +// eslint-disable-next-line vue/multi-word-component-names +// eslint-disable-next-line unicorn/filename-case diff --git a/docs/.vitepress/components/Paramdatatype.vue b/docs/.vitepress/components/Paramdatatype.vue deleted file mode 100644 index ddd29ae8e..000000000 --- a/docs/.vitepress/components/Paramdatatype.vue +++ /dev/null @@ -1,18 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/.vitepress/components/Paramtype.vue b/docs/.vitepress/components/Paramtype.vue deleted file mode 100644 index d9ca35312..000000000 --- a/docs/.vitepress/components/Paramtype.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - - \ No newline at end of file diff --git a/docs/.vitepress/components/Endpoint.vue b/docs/.vitepress/components/endpoint.vue similarity index 54% rename from docs/.vitepress/components/Endpoint.vue rename to docs/.vitepress/components/endpoint.vue index e6704ac67..4485181c3 100644 --- a/docs/.vitepress/components/Endpoint.vue +++ b/docs/.vitepress/components/endpoint.vue @@ -1,3 +1,8 @@ + +// eslint-disable-next-line vue/multi-word-component-names +// eslint-disable-next-line unicorn/filename-case +// eslint-disable-next-line vue/multi-word-component-names +// eslint-disable-next-line unicorn/filename-case

Response


{{ response.type }}
@@ -172,8 +157,8 @@ diff --git a/docs/.vitepress/components/Hint.vue b/docs/.vitepress/components/hint.vue similarity index 74% rename from docs/.vitepress/components/Hint.vue rename to docs/.vitepress/components/hint.vue index 780526eba..f3948d770 100644 --- a/docs/.vitepress/components/Hint.vue +++ b/docs/.vitepress/components/hint.vue @@ -1,3 +1,8 @@ + +// eslint-disable-next-line vue/multi-word-component-names +// eslint-disable-next-line unicorn/filename-case +// eslint-disable-next-line vue/multi-word-component-names +// eslint-disable-next-line unicorn/filename-case - diff --git a/frontend/src/components/actions/action-definition-drawer.vue b/frontend/src/components/actions/action-definition-drawer.vue new file mode 100644 index 000000000..a58156dd3 --- /dev/null +++ b/frontend/src/components/actions/action-definition-drawer.vue @@ -0,0 +1,522 @@ + + + diff --git a/frontend/src/components/actions/action-details-execution-tab.vue b/frontend/src/components/actions/action-details-execution-tab.vue new file mode 100644 index 000000000..4742a9eb4 --- /dev/null +++ b/frontend/src/components/actions/action-details-execution-tab.vue @@ -0,0 +1,464 @@ + + + diff --git a/frontend/src/components/actions/action-details-template-tab.vue b/frontend/src/components/actions/action-details-template-tab.vue new file mode 100644 index 000000000..6cd0382ef --- /dev/null +++ b/frontend/src/components/actions/action-details-template-tab.vue @@ -0,0 +1,135 @@ + + + diff --git a/frontend/src/components/actions/action-executions.vue b/frontend/src/components/actions/action-executions.vue new file mode 100644 index 000000000..7a4ec07ba --- /dev/null +++ b/frontend/src/components/actions/action-executions.vue @@ -0,0 +1,53 @@ + + + diff --git a/frontend/src/components/actions/action-launch-drawer.vue b/frontend/src/components/actions/action-launch-drawer.vue new file mode 100644 index 000000000..9a9302c62 --- /dev/null +++ b/frontend/src/components/actions/action-launch-drawer.vue @@ -0,0 +1,334 @@ + + + diff --git a/frontend/src/components/actions/action-revisions-drawer.vue b/frontend/src/components/actions/action-revisions-drawer.vue new file mode 100644 index 000000000..9916e0cf2 --- /dev/null +++ b/frontend/src/components/actions/action-revisions-drawer.vue @@ -0,0 +1,146 @@ + + + diff --git a/frontend/src/components/actions/action-runtime.vue b/frontend/src/components/actions/action-runtime.vue new file mode 100644 index 000000000..65983dc09 --- /dev/null +++ b/frontend/src/components/actions/action-runtime.vue @@ -0,0 +1,68 @@ + + + diff --git a/frontend/src/components/actions/action-store.vue b/frontend/src/components/actions/action-store.vue new file mode 100644 index 000000000..85be84875 --- /dev/null +++ b/frontend/src/components/actions/action-store.vue @@ -0,0 +1,386 @@ + + + diff --git a/frontend/src/components/actions-table.vue b/frontend/src/components/actions/actions-table.vue similarity index 69% rename from frontend/src/components/actions-table.vue rename to frontend/src/components/actions/actions-table.vue index 367dd477e..fbd0c2a39 100644 --- a/frontend/src/components/actions-table.vue +++ b/frontend/src/components/actions/actions-table.vue @@ -6,7 +6,7 @@ :columns="columns as any" :rows-per-page-options="[10, 20, 50, 100]" row-key="uuid" - :loading="loading" + :loading="isLoading" flat bordered binary-state-sort @@ -41,15 +41,17 @@ @@ -73,7 +75,11 @@ clickable @click=" () => - router.push('action/' + props.row.uuid) + router.push({ + name: ROUTES.ANALYSIS_DETAILS + .routeName, + params: { id: props.row.uuid }, + }) " > View Details @@ -98,41 +104,46 @@ diff --git a/frontend/src/components/actions/compute-resources-fieldset.vue b/frontend/src/components/actions/compute-resources-fieldset.vue new file mode 100644 index 000000000..43ed49799 --- /dev/null +++ b/frontend/src/components/actions/compute-resources-fieldset.vue @@ -0,0 +1,98 @@ + + + diff --git a/frontend/src/components/actions/delete-action-template.vue b/frontend/src/components/actions/delete-action-template.vue new file mode 100644 index 000000000..5ab403f67 --- /dev/null +++ b/frontend/src/components/actions/delete-action-template.vue @@ -0,0 +1,74 @@ + + + diff --git a/frontend/src/components/actions/delete-action.vue b/frontend/src/components/actions/delete-action.vue new file mode 100644 index 000000000..417163756 --- /dev/null +++ b/frontend/src/components/actions/delete-action.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/frontend/src/components/running-actions.vue b/frontend/src/components/actions/running-actions.vue similarity index 79% rename from frontend/src/components/running-actions.vue rename to frontend/src/components/actions/running-actions.vue index 822c88192..151b97fc3 100644 --- a/frontend/src/components/running-actions.vue +++ b/frontend/src/components/actions/running-actions.vue @@ -1,6 +1,5 @@ - + diff --git a/frontend/src/components/cli-links/klein-download-file.vue b/frontend/src/components/cli-links/klein-download-file.vue index da6df5703..881a7f6fe 100644 --- a/frontend/src/components/cli-links/klein-download-file.vue +++ b/frontend/src/components/cli-links/klein-download-file.vue @@ -38,7 +38,7 @@ diff --git a/frontend/src/components/common/app-input.vue b/frontend/src/components/common/app-input.vue new file mode 100644 index 000000000..15bf9a318 --- /dev/null +++ b/frontend/src/components/common/app-input.vue @@ -0,0 +1,54 @@ + + + diff --git a/frontend/src/components/common/app-refresh-button.vue b/frontend/src/components/common/app-refresh-button.vue new file mode 100644 index 000000000..44b25f60a --- /dev/null +++ b/frontend/src/components/common/app-refresh-button.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/frontend/src/components/common/app-search-bar.vue b/frontend/src/components/common/app-search-bar.vue new file mode 100644 index 000000000..ac8042fd4 --- /dev/null +++ b/frontend/src/components/common/app-search-bar.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/frontend/src/components/common/app-select.vue b/frontend/src/components/common/app-select.vue new file mode 100644 index 000000000..6ef213b19 --- /dev/null +++ b/frontend/src/components/common/app-select.vue @@ -0,0 +1,60 @@ + + + diff --git a/frontend/src/components/common/drawer-header.vue b/frontend/src/components/common/drawer-header.vue new file mode 100644 index 000000000..8d9282fa6 --- /dev/null +++ b/frontend/src/components/common/drawer-header.vue @@ -0,0 +1,36 @@ + + + diff --git a/frontend/src/components/common/scope-selector.vue b/frontend/src/components/common/scope-selector.vue new file mode 100644 index 000000000..a2808c120 --- /dev/null +++ b/frontend/src/components/common/scope-selector.vue @@ -0,0 +1,191 @@ + + + diff --git a/frontend/src/components/common/smooth-loading.vue b/frontend/src/components/common/smooth-loading.vue new file mode 100644 index 000000000..e7d4f0c30 --- /dev/null +++ b/frontend/src/components/common/smooth-loading.vue @@ -0,0 +1,61 @@ + + + diff --git a/frontend/src/components/configure-access-rights/access-group-avatar.vue b/frontend/src/components/configure-access-rights/access-group-avatar.vue index 33122f9e3..8edd14818 100644 --- a/frontend/src/components/configure-access-rights/access-group-avatar.vue +++ b/frontend/src/components/configure-access-rights/access-group-avatar.vue @@ -8,7 +8,7 @@ + + - diff --git a/frontend/src/components/create-tag-type.vue b/frontend/src/components/create-tag-type.vue index 7c892514e..1f38d4635 100644 --- a/frontend/src/components/create-tag-type.vue +++ b/frontend/src/components/create-tag-type.vue @@ -42,7 +42,7 @@ - diff --git a/frontend/src/components/delete-file.vue b/frontend/src/components/delete-file.vue index ac7a320bc..23bab3e8a 100644 --- a/frontend/src/components/delete-file.vue +++ b/frontend/src/components/delete-file.vue @@ -19,7 +19,7 @@ import { deleteFile } from 'src/services/mutations/file'; import { ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; -import { FileWithTopicDto } from '@api/types/file/file.dto'; +import type { FileWithTopicDto } from '@kleinkram/api-dto/types/file/file.dto'; const fileNameCheck = ref(''); const client = useQueryClient(); @@ -82,6 +82,8 @@ const properties = defineProps<{ defineExpose({ deleteFileAction, + + // eslint-disable-next-line @typescript-eslint/naming-convention file_name_check: fileNameCheck, }); diff --git a/frontend/src/components/delete-mission.vue b/frontend/src/components/delete-mission.vue index 4f17d23a5..1a746daeb 100644 --- a/frontend/src/components/delete-mission.vue +++ b/frontend/src/components/delete-mission.vue @@ -13,7 +13,7 @@ diff --git a/frontend/src/components/delete-project.vue b/frontend/src/components/delete-project.vue index 01a16834a..16f7c8b17 100644 --- a/frontend/src/components/delete-project.vue +++ b/frontend/src/components/delete-project.vue @@ -19,7 +19,7 @@ import { useHandler } from 'src/hooks/query-hooks'; import { deleteProject } from 'src/services/mutations/project'; import { ref } from 'vue'; -import { ProjectWithCreator } from '@api/types/project/project-with-creator.dto'; +import type { ProjectWithCreator } from '@kleinkram/api-dto/types/project/project-with-creator.dto'; const projectNameCheck = ref(''); const client = useQueryClient(); @@ -70,6 +70,8 @@ async function deleteProjectAction(): Promise { defineExpose({ deleteProjectAction, + + // eslint-disable-next-line @typescript-eslint/naming-convention project_name_check: projectNameCheck, }); diff --git a/frontend/src/components/documentation-icon.vue b/frontend/src/components/documentation-icon.vue index 2588c3959..d620f9fa5 100644 --- a/frontend/src/components/documentation-icon.vue +++ b/frontend/src/components/documentation-icon.vue @@ -17,5 +17,5 @@ const documentationBasePath = 'https://docs.datasets.leggedrobotics.com'; const documentationDefaultPath = '/usage/getting-started.html'; const documentationLink = - link || documentationBasePath + documentationDefaultPath; + link ?? documentationBasePath + documentationDefaultPath; diff --git a/frontend/src/components/edit-file.vue b/frontend/src/components/edit-file.vue index 1eac7771e..c27b4fabd 100644 --- a/frontend/src/components/edit-file.vue +++ b/frontend/src/components/edit-file.vue @@ -1,6 +1,6 @@ - - - diff --git a/frontend/src/components/edit-mission.vue b/frontend/src/components/edit-mission.vue index 10ef2dc30..f41ce574f 100644 --- a/frontend/src/components/edit-mission.vue +++ b/frontend/src/components/edit-mission.vue @@ -25,13 +25,14 @@ + + diff --git a/frontend/src/components/info-banner.vue b/frontend/src/components/info-banner.vue new file mode 100644 index 000000000..03bb78425 --- /dev/null +++ b/frontend/src/components/info-banner.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/frontend/src/components/inspect-file/file-detail.vue b/frontend/src/components/inspect-file/file-detail.vue index 489d1fc30..64e988d5d 100644 --- a/frontend/src/components/inspect-file/file-detail.vue +++ b/frontend/src/components/inspect-file/file-detail.vue @@ -6,6 +6,7 @@ @copy-link="copyPublicLink" @copy-hash="copyHash" @copy-uuid="copyUuid" + @copy-foxglove="copyFoxgloveLink" />
@@ -25,8 +26,41 @@
+ +
+

+ Trajectory Preview + + + Preview functionality is currently in beta. + + +

+
+ +
+
+ Loading content... +
+
+ + +
+ +
+ -
+
+ +
+

SQL Schema

+
+ + Loading schema... +
+
+
{{
+                        preview.dbSchema.value
+                            ?.replace(/CREATE TABLE/g, '\nCREATE TABLE')
+                            .trim()
+                    }}
+
+
- +
- -
No Preview Available
-
- Preview not supported for - .{{ fileExtension }} -
+ +
No preview available while uploading
+
+ + +
+
- +
diff --git a/frontend/src/components/inspect-file/file-header.vue b/frontend/src/components/inspect-file/file-header.vue index f6b712643..01e8981a0 100644 --- a/frontend/src/components/inspect-file/file-header.vue +++ b/frontend/src/components/inspect-file/file-header.vue @@ -24,47 +24,61 @@ + + + + + Copy Foxglove link + + + + - - Copy public link + + + + + Copy public link + - + + + Copy MD5 - + + + Copy UUID - + + + Project
{{ file?.mission.project.name }} - {{ - file?.mission.project.name - }} + + {{ file?.mission.project.name }} +
@@ -155,7 +169,8 @@ diff --git a/frontend/src/components/inspect-file/viewers/camera-info-viewer.vue b/frontend/src/components/inspect-file/viewers/camera-info-viewer.vue index 8ca752e16..0ea034b4a 100644 --- a/frontend/src/components/inspect-file/viewers/camera-info-viewer.vue +++ b/frontend/src/components/inspect-file/viewers/camera-info-viewer.vue @@ -164,6 +164,7 @@ import { Notify, copyToClipboard as quasarCopy } from 'quasar'; import { onMounted } from 'vue'; const properties = defineProps<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any messages: any[]; totalCount: number; topicName: string; @@ -172,6 +173,7 @@ const properties = defineProps<{ const emit = defineEmits(['load-required', 'load-more']); onMounted(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!properties.messages || properties.messages.length === 0) { emit('load-required'); } @@ -191,7 +193,9 @@ const formatTime = (nano: bigint): string => { return timePart?.replace('Z', '') ?? 'Invalid Time'; }; +// eslint-disable-next-line @typescript-eslint/naming-convention const fmt = (number_: number): string => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (number_ === undefined || number_ === null) return '-'; // If integer, show integer, else 4 decimals if (Number.isInteger(number_)) return String(number_); @@ -199,21 +203,30 @@ const fmt = (number_: number): string => { }; // Handle both Arrays and Object-style arrays {"0": val, "1": val} +// eslint-disable-next-line @typescript-eslint/no-explicit-any const getArray = (data: any): number[] => { if (!data) return []; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return if (Array.isArray(data)) return data; // Convert object keys "0", "1", "2" to array - return Object.keys(data) - .map(Number) - .sort((a, b) => a - b) - .map((k) => data[k]); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return ( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + Object.keys(data) + .map(Number) + .sort((a, b) => a - b) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return + .map((k) => data[k]) + ); }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const formatD = (data: any): string => { const array = getArray(data); return array.map((v) => fmt(v)).join(', '); }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any async function copyRaw(data: any): Promise { await quasarCopy(JSON.stringify(data, null, 2)); Notify.create({ diff --git a/frontend/src/components/inspect-file/viewers/common/viewer-layout.vue b/frontend/src/components/inspect-file/viewers/common/viewer-layout.vue new file mode 100644 index 000000000..65cd58901 --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/common/viewer-layout.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/grid-map-viewer.vue b/frontend/src/components/inspect-file/viewers/grid-map-viewer.vue new file mode 100644 index 000000000..9b18935cc --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/grid-map-viewer.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/image-sequence-viewer.vue b/frontend/src/components/inspect-file/viewers/image-sequence-viewer.vue index f89f510f8..239a4f1af 100644 --- a/frontend/src/components/inspect-file/viewers/image-sequence-viewer.vue +++ b/frontend/src/components/inspect-file/viewers/image-sequence-viewer.vue @@ -4,10 +4,18 @@ >
- +
+ Magic bytes: {{ magicBytes }} +
+
Size: {{ frameSize }}
+
+ +
+ +
diff --git a/frontend/src/components/inspect-file/viewers/imu-viewer.vue b/frontend/src/components/inspect-file/viewers/imu-viewer.vue new file mode 100644 index 000000000..cbbe4983d --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/imu-viewer.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/json-log-viewer.vue b/frontend/src/components/inspect-file/viewers/json-log-viewer.vue index 4c01081b5..fa6fb4b74 100644 --- a/frontend/src/components/inspect-file/viewers/json-log-viewer.vue +++ b/frontend/src/components/inspect-file/viewers/json-log-viewer.vue @@ -83,6 +83,7 @@ import { Notify, copyToClipboard as quasarCopy } from 'quasar'; import { onMounted } from 'vue'; const properties = defineProps<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any messages: any[]; totalCount: number; topicName: string; @@ -90,6 +91,7 @@ const properties = defineProps<{ const emit = defineEmits(['load-required', 'load-more']); onMounted(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!properties.messages || properties.messages.length === 0) emit('load-required'); }); @@ -107,16 +109,20 @@ const formatTime = (nano: bigint): string => { return timePart?.replace('Z', '') ?? 'Invalid Time'; }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const getByteSize = (data: any): number => { if (!data) return 0; if (data instanceof Uint8Array) return data.byteLength; - return JSON.stringify(data).length; + return JSON.stringify(data, replacer).length; }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const formatContent = (data: any): string => { if (data === null) return '[Empty]'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (data instanceof Uint8Array || data?.type === 'Buffer') - return `[Binary Data] Size: ${data.byteLength || 0} bytes`; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions + return `[Binary Data] Size: ${data.byteLength ?? 0} bytes`; try { const jsonString = JSON.stringify(data, replacer, 2); return jsonString.length > 2000 @@ -127,13 +133,21 @@ const formatContent = (data: any): string => { } }; -const replacer = (_key: string, value: any) => - Array.isArray(value) && value.length > 20 - ? `[Array(${value.length})]` +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const replacer = (_key: string, value: any) => { + if (typeof value === 'bigint') { + return value.toString(); + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Array.isArray(value) && value.length > 20 + ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `[Array(${value.length})]` : value; +}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any async function copyToClipboard(data: any): Promise { - await quasarCopy(JSON.stringify(data, null, 2)); + await quasarCopy(JSON.stringify(data, replacer, 2)); Notify.create({ message: 'Payload copied', color: 'positive', diff --git a/frontend/src/components/inspect-file/viewers/nav-sat-fix-viewer.vue b/frontend/src/components/inspect-file/viewers/nav-sat-fix-viewer.vue new file mode 100644 index 000000000..6432499d7 --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/nav-sat-fix-viewer.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/odometry-viewer.vue b/frontend/src/components/inspect-file/viewers/odometry-viewer.vue new file mode 100644 index 000000000..0e1796b03 --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/odometry-viewer.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/path-viewer.vue b/frontend/src/components/inspect-file/viewers/path-viewer.vue new file mode 100644 index 000000000..5ef655560 --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/path-viewer.vue @@ -0,0 +1,533 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/playback-controls.vue b/frontend/src/components/inspect-file/viewers/playback-controls.vue index 2cfc4486f..20a6742b0 100644 --- a/frontend/src/components/inspect-file/viewers/playback-controls.vue +++ b/frontend/src/components/inspect-file/viewers/playback-controls.vue @@ -61,6 +61,7 @@ const onNext = (): void => { }; // Quasar sliders can emit null, so we type 'value' loosely or as number | null +// eslint-disable-next-line @typescript-eslint/no-explicit-any const onUpdateModelValue = (value: any): void => { emit('update:modelValue', value); }; diff --git a/frontend/src/components/inspect-file/viewers/point-cloud-viewer.vue b/frontend/src/components/inspect-file/viewers/point-cloud-viewer.vue index 28c1968a5..57187c4cf 100644 --- a/frontend/src/components/inspect-file/viewers/point-cloud-viewer.vue +++ b/frontend/src/components/inspect-file/viewers/point-cloud-viewer.vue @@ -125,6 +125,7 @@ import { Notify, copyToClipboard as quasarCopy } from 'quasar'; import { computed, onMounted, ref, watch } from 'vue'; const properties = defineProps<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any messages: any[]; totalCount: number; topicName: string; @@ -134,8 +135,6 @@ const emit = defineEmits(['load-required', 'load-more']); const canvasReference = ref(null); const currentIndex = ref(0); -const CANVAS_SIZE = 600; - const incrementIndex = (): void => { if (currentIndex.value < properties.messages.length - 1) { currentIndex.value++; @@ -156,15 +155,20 @@ const lastMouse = ref({ x: 0, y: 0 }); // --- Computed Data Access --- const currentMessage = computed( - () => properties.messages[currentIndex.value] || null, + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + () => properties.messages[currentIndex.value] ?? null, ); -const width = computed(() => currentMessage.value?.data?.width || 0); -const height = computed(() => currentMessage.value?.data?.height || 0); +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return +const width = computed(() => currentMessage.value?.data?.width ?? 0); +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return +const height = computed(() => currentMessage.value?.data?.height ?? 0); const frameId = computed( - () => currentMessage.value?.data?.header?.frame_id || '', + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return + () => currentMessage.value?.data?.header?.frame_id ?? '', ); onMounted(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!properties.messages || properties.messages.length === 0) { emit('load-required'); } else { @@ -234,65 +238,124 @@ interface Point { z: number; } +const renderError = ref(null); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any function parsePoints(message: any): Point[] { + renderError.value = null; if (!message) return []; - const fields = message.fields as any[]; - const data = message.data as Uint8Array | number[]; - const pointStep = message.point_step as number; - const isBigEndian = message.is_bigendian; - const totalPoints = message.width * message.height; - - const bytes = data instanceof Uint8Array ? data : new Uint8Array(data); - const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); - - const xField = fields.find((f: any) => f.name === 'x'); - const yField = fields.find((f: any) => f.name === 'y'); - const zField = fields.find((f: any) => f.name === 'z'); - - if (!xField || !yField) return []; - - const xOff: number = xField.offset; - const yOff: number = yField.offset; - const zOff: number = zField ? zField.offset : -1; - const isFloat32 = xField.datatype === 7; - - const points: Point[] = []; - - for (let index = 0; index < totalPoints; index++) { - const base = index * pointStep; - if (base + pointStep > bytes.length) break; - - let x, - y, - z = 0; - if (isFloat32) { - x = view.getFloat32(base + xOff, !isBigEndian); - y = view.getFloat32(base + yOff, !isBigEndian); - if (zOff >= 0) z = view.getFloat32(base + zOff, !isBigEndian); - } else { - x = view.getFloat64(base + xOff, !isBigEndian); - y = view.getFloat64(base + yOff, !isBigEndian); - if (zOff >= 0) z = view.getFloat64(base + zOff, !isBigEndian); + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + const fields = message.fields as any[]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!fields || !Array.isArray(fields)) { + throw new Error('Message missing "fields" array'); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const data = message.data as Uint8Array | number[]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const pointStep = message.point_step as number; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const isBigEndian = message.is_bigendian; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const totalPoints = message.width * message.height; + + const bytes = data instanceof Uint8Array ? data : new Uint8Array(data); + const view = new DataView( + bytes.buffer, + bytes.byteOffset, + bytes.byteLength, + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const xField = fields.find((f: any) => f.name === 'x'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const yField = fields.find((f: any) => f.name === 'y'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const zField = fields.find((f: any) => f.name === 'z'); + + if (!xField || !yField) { + throw new Error('Missing x or y fields in PointCloud2'); } - if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) - continue; - points.push({ x, y, z }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const xOff: number = xField.offset; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const yOff: number = yField.offset; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const zOff: number = zField ? zField.offset : -1; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const isFloat32 = xField.datatype === 7; + + const points: Point[] = []; + + for (let index = 0; index < totalPoints; index++) { + const base = index * pointStep; + if (base + pointStep > bytes.length) break; + + let x, + y, + z = 0; + if (isFloat32) { + x = view.getFloat32(base + xOff, !isBigEndian); + y = view.getFloat32(base + yOff, !isBigEndian); + if (zOff >= 0) z = view.getFloat32(base + zOff, !isBigEndian); + } else { + x = view.getFloat64(base + xOff, !isBigEndian); + y = view.getFloat64(base + yOff, !isBigEndian); + if (zOff >= 0) z = view.getFloat64(base + zOff, !isBigEndian); + } + + if ( + !Number.isFinite(x) || + !Number.isFinite(y) || + !Number.isFinite(z) + ) + continue; + points.push({ x, y, z }); + } + return points; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + console.error('Error parsing PointCloud2:', error); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + renderError.value = error.message; + return []; } - return points; } // --- Rendering Logic --- +// --- Rendering Logic --- + +// eslint-disable-next-line complexity function renderCloud() { const canvas = canvasReference.value; if (!canvas || !currentMessage.value) return; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const points = parsePoints(currentMessage.value.data); if (points.length === 0) return; - canvas.width = CANVAS_SIZE; - canvas.height = CANVAS_SIZE; + // Use parent container dimensions or window-based max + // For now, let's set it to a reasonable large size or dynamic + // But to fill the area, we need to know the container size. + // Let's rely on the canvas's clientWidth/Height after CSS layout. + + // We need to set the internal resolution to match display size for sharpness + // or keep it fixed. Let's try to match display size. + const displayWidth = canvas.clientWidth || 800; + const displayHeight = canvas.clientHeight || 600; + + // Update resolution if changed + if (canvas.width !== displayWidth || canvas.height !== displayHeight) { + canvas.width = displayWidth; + canvas.height = displayHeight; + } + + const canvasWidth = canvas.width; + const canvasHeight = canvas.height; const context = canvas.getContext('2d'); if (!context) return; @@ -318,8 +381,10 @@ function renderCloud() { const rangeZ = maxZ - minZ || 1; // Base Scale (Auto-Fit) - const maxRange = Math.max(rangeX, rangeY); - const baseScale = (CANVAS_SIZE - 40) / maxRange; + // Fit to the smallest dimension + const scaleX = (canvasWidth - 40) / rangeX; + const scaleY = (canvasHeight - 40) / rangeY; + const baseScale = Math.min(scaleX, scaleY); // Apply User Zoom const finalScale = baseScale * userZoom.value; @@ -331,24 +396,28 @@ function renderCloud() { // Center logic: (Canvas - Content) / 2 // We subtract minX * scale to shift the local 0,0 to the start of the data const offsetX = - (CANVAS_SIZE - contentWidth) / 2 - minX * finalScale + userPan.value.x; + (canvasWidth - contentWidth) / 2 - minX * finalScale + userPan.value.x; // Y is inverted (Top-Down Map). Standard is Up=Y. Canvas is Down=Y. // We anchor to the bottom of the content area to flip it correctly? // Let's use standard flip logic: canvas_y = H - (world_y * scale + off_y) + // Wait, if we center it, we just need to flip the Y coordinate relative to the center. + // Let's stick to the previous logic but with dynamic height. const offsetY = - (CANVAS_SIZE - contentHeight) / 2 - minY * finalScale - userPan.value.y; + (canvasHeight - contentHeight) / 2 - + minY * finalScale - + userPan.value.y; // 2. Draw - const imgData = context.createImageData(CANVAS_SIZE, CANVAS_SIZE); + const imgData = context.createImageData(canvasWidth, canvasHeight); const data = imgData.data; for (const p of points) { const cx = Math.floor(p.x * finalScale + offsetX); - const cy = Math.floor(CANVAS_SIZE - (p.y * finalScale + offsetY)); + const cy = Math.floor(canvasHeight - (p.y * finalScale + offsetY)); - if (cx >= 0 && cx < CANVAS_SIZE && cy >= 0 && cy < CANVAS_SIZE) { - const index = (cy * CANVAS_SIZE + cx) * 4; + if (cx >= 0 && cx < canvasWidth && cy >= 0 && cy < canvasHeight) { + const index = (cy * canvasWidth + cx) * 4; // Z-Coloring const zn = (p.z - minZ) / rangeZ; @@ -371,25 +440,27 @@ function renderCloud() { context.globalAlpha = 0.3; const originX = Math.floor(0 * finalScale + offsetX); - const originY = Math.floor(CANVAS_SIZE - (0 * finalScale + offsetY)); + const originY = Math.floor(canvasHeight - (0 * finalScale + offsetY)); - if (originX >= 0 && originX <= CANVAS_SIZE) { + if (originX >= 0 && originX <= canvasWidth) { context.beginPath(); context.moveTo(originX, 0); - context.lineTo(originX, CANVAS_SIZE); + context.lineTo(originX, canvasHeight); context.stroke(); } - if (originY >= 0 && originY <= CANVAS_SIZE) { + if (originY >= 0 && originY <= canvasHeight) { context.beginPath(); context.moveTo(0, originY); - context.lineTo(CANVAS_SIZE, originY); + context.lineTo(canvasWidth, originY); context.stroke(); } } async function copyRaw(): Promise { if (!currentMessage.value) return; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const meta = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access ...currentMessage.value.data, data: '[Binary Blob Omitted]', }; @@ -413,8 +484,8 @@ const loadMore = (): void => { .pc-canvas { width: 100%; height: 100%; - max-width: 600px; - max-height: 600px; + /* max-width: 600px; */ + /* max-height: 600px; */ image-rendering: pixelated; cursor: move; /* Indicate draggable */ } diff --git a/frontend/src/components/inspect-file/viewers/point-stamped-viewer.vue b/frontend/src/components/inspect-file/viewers/point-stamped-viewer.vue new file mode 100644 index 000000000..c2186955a --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/point-stamped-viewer.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/pose-stamped-viewer.vue b/frontend/src/components/inspect-file/viewers/pose-stamped-viewer.vue new file mode 100644 index 000000000..25653e12b --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/pose-stamped-viewer.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/ros-log-viewer.vue b/frontend/src/components/inspect-file/viewers/ros-log-viewer.vue index 79bce23a6..c5f4c0e4a 100644 --- a/frontend/src/components/inspect-file/viewers/ros-log-viewer.vue +++ b/frontend/src/components/inspect-file/viewers/ros-log-viewer.vue @@ -23,54 +23,51 @@ - - +
- - - - - -
- - {{ msg.data.name }} - - - {{ formatTime(msg.logTime) }} - -
- -
- {{ msg.data.msg }} -
- -
- {{ msg.data.file }}:{{ msg.data.line }} - ({{ msg.data.function }}) -
-
- - + + + [{{ formatTime(msg.logTime) }}] + + + + + [{{ getLevelLabel(msg.data.level) }}] + + + + + [{{ msg.data.name }}] + + + + + {{ msg.data.msg }} + +
+
-
- Showing {{ messages.length }} / {{ totalCount }} logs. -
+ import { Notify, copyToClipboard as quasarCopy } from 'quasar'; import { onMounted } from 'vue'; +import SmoothLoading from '../../common/smooth-loading.vue'; const properties = defineProps<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any messages: any[]; totalCount: number; topicName: string; @@ -97,6 +96,7 @@ const properties = defineProps<{ const emit = defineEmits(['load-required', 'load-more']); onMounted(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!properties.messages || properties.messages.length === 0) emit('load-required'); }); @@ -115,48 +115,44 @@ const formatTime = (nano: bigint): string => { }; // ROS Log Levels: 1=Debug, 2=Info, 4=Warn, 8=Error, 16=Fatal -const getLevelColor = (level: number): string => { +const getLevelClass = (level: number): string => { switch (level) { - case 1: { - return 'grey'; - } // DEBUG + case 1: case 2: { - return 'positive'; - } // INFO + return 'text-grey-7'; + } // DEBUG, INFO case 4: { - return 'warning'; + return 'text-orange-9'; } // WARN - case 8: { - return 'negative'; - } // ERROR + case 8: case 16: { - return 'purple'; - } // FATAL + return 'text-negative'; + } // ERROR, FATAL default: { - return 'grey-8'; + return 'text-grey-8'; } } }; -const getLevelIcon = (level: number): string => { +const getLevelLabel = (level: number): string => { switch (level) { case 1: { - return 'sym_o_bug_report'; + return 'DEBUG'; } case 2: { - return 'sym_o_info'; + return 'INFO'; } case 4: { - return 'sym_o_warning'; + return 'WARN'; } case 8: { - return 'sym_o_error'; + return 'ERROR'; } case 16: { - return 'sym_o_dangerous'; + return 'FATAL'; } default: { - return 'sym_o_help'; + return 'UNK'; } } }; diff --git a/frontend/src/components/inspect-file/viewers/simple-time-chart.vue b/frontend/src/components/inspect-file/viewers/simple-time-chart.vue index 579e40b14..22aa2e6ef 100644 --- a/frontend/src/components/inspect-file/viewers/simple-time-chart.vue +++ b/frontend/src/components/inspect-file/viewers/simple-time-chart.vue @@ -18,7 +18,12 @@
-
+
+ + +
@@ -50,6 +67,33 @@ {{ fmt(yRange.min) }}
+ +
+
+
+ {{ hoverTime?.toFixed(3) }}s +
+
+ {{ absoluteTime }} +
+
+
+
+
{{ p.name }}: {{ p.value.toFixed(4) }}
+
+
+
Subsampled @@ -62,7 +106,7 @@ diff --git a/frontend/src/components/inspect-file/viewers/skeleton-time-chart.vue b/frontend/src/components/inspect-file/viewers/skeleton-time-chart.vue new file mode 100644 index 000000000..4b1b0f94d --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/skeleton-time-chart.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/statistics-viewer.vue b/frontend/src/components/inspect-file/viewers/statistics-viewer.vue new file mode 100644 index 000000000..993cff6a7 --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/statistics-viewer.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/string-viewer.vue b/frontend/src/components/inspect-file/viewers/string-viewer.vue index 3a9ddcb33..70757717c 100644 --- a/frontend/src/components/inspect-file/viewers/string-viewer.vue +++ b/frontend/src/components/inspect-file/viewers/string-viewer.vue @@ -27,16 +27,60 @@ class="q-py-sm" > -
-
+
+
{{ formatTime(msg.logTime) }}
-
- {{ msg.data.data }} +
+
+ {{ parseContent(msg.data.data).prefix }} +
+ +
+
{{
+                                        parseContent(msg.data.data).json
+                                    }}
+ + Copy JSON + +
+ +
+ {{ parseContent(msg.data.data).suffix }} +
+
+ + +
+ {{ getDisplayText(msg.data.data) }}
@@ -47,9 +91,11 @@ v-if="messages.length < totalCount" class="text-center q-pa-md bg-grey-1" > -
- Showing {{ messages.length }} / {{ totalCount }} messages. -
+ import { Notify, copyToClipboard as quasarCopy } from 'quasar'; import { onMounted } from 'vue'; +import SmoothLoading from '../../common/smooth-loading.vue'; const properties = defineProps<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any messages: any[]; totalCount: number; topicName: string; @@ -76,10 +124,105 @@ const properties = defineProps<{ const emit = defineEmits(['load-required', 'load-more']); onMounted(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!properties.messages || properties.messages.length === 0) emit('load-required'); }); +// --- JSON Logic --- + +interface ParsedContent { + prefix: string; + json: string; + suffix: string; + isJson: boolean; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +const parseContent = (string_: string): ParsedContent => { + if (!string_ || typeof string_ !== 'string') { + return { prefix: string_ || '', json: '', suffix: '', isJson: false }; + } + + // 1. Try full JSON first + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const object = JSON.parse(string_); + return { + prefix: '', + json: JSON.stringify(object, null, 2), + suffix: '', + isJson: true, + }; + } catch { + // Continue to inline check + } + + // 2. Try to find JSON object/array substring + // Look for first { or [ and last } or ] + const firstBrace = string_.indexOf('{'); + const firstBracket = string_.indexOf('['); + let start = -1; + + if (firstBrace !== -1 && firstBracket !== -1) { + start = Math.min(firstBrace, firstBracket); + } else if (firstBrace !== -1) { + start = firstBrace; + } else if (firstBracket !== -1) { + start = firstBracket; + } + + if (start === -1) { + return { prefix: string_, json: '', suffix: '', isJson: false }; + } + + // Find end + const lastBrace = string_.lastIndexOf('}'); + const lastBracket = string_.lastIndexOf(']'); + let end = -1; + + if (lastBrace !== -1 && lastBracket !== -1) { + end = Math.max(lastBrace, lastBracket); + } else if (lastBrace !== -1) { + end = lastBrace; + } else if (lastBracket !== -1) { + end = lastBracket; + } + + if (end === -1 || end <= start) { + return { prefix: string_, json: '', suffix: '', isJson: false }; + } + + const potentialJson = string_.slice(start, end + 1); + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const object = JSON.parse(potentialJson); + return { + prefix: string_.slice(0, start), + json: JSON.stringify(object, null, 2), + suffix: string_.slice(end + 1), + isJson: true, + }; + } catch { + return { prefix: string_, json: '', suffix: '', isJson: false }; + } +}; + +// --- Truncation Logic --- +const MAX_LENGTH = 300; + +const shouldTruncate = (text: string): boolean => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return text?.length > MAX_LENGTH; +}; + +const getDisplayText = (text: string): string => { + if (!shouldTruncate(text)) { + return text; + } + return `${text.slice(0, MAX_LENGTH)}...`; +}; + // --- Formatters --- const formatTime = (nano: bigint): string => { const ms = Number(nano / 1_000_000n); @@ -101,6 +244,14 @@ async function copyRaw(): Promise { timeout: 1000, }); } +async function copyText(text: string): Promise { + await quasarCopy(text); + Notify.create({ + message: 'Copied to clipboard', + color: 'positive', + timeout: 1000, + }); +} const loadMore = (): void => { emit('load-more'); @@ -120,5 +271,6 @@ const loadMore = (): void => { .break-word { word-wrap: break-word; white-space: pre-wrap; + word-break: break-all; } diff --git a/frontend/src/components/inspect-file/viewers/svo2-viewer.vue b/frontend/src/components/inspect-file/viewers/svo2-viewer.vue new file mode 100644 index 000000000..9d56b2aa6 --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/svo2-viewer.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/temperature-viewer.vue b/frontend/src/components/inspect-file/viewers/temperature-viewer.vue index 00952fbce..e1e8c9bc1 100644 --- a/frontend/src/components/inspect-file/viewers/temperature-viewer.vue +++ b/frontend/src/components/inspect-file/viewers/temperature-viewer.vue @@ -12,7 +12,7 @@ {{ duration.toFixed(2) }}s - {{ messages.length }} samples + {{ messages.length }} Messages
- - -
-
- Showing {{ messages.length }} / {{ totalCount }} points. -
- + +
+
+
@@ -52,10 +48,12 @@ diff --git a/frontend/src/components/inspect-file/viewers/tum-viewer.vue b/frontend/src/components/inspect-file/viewers/tum-viewer.vue new file mode 100644 index 000000000..3e220811d --- /dev/null +++ b/frontend/src/components/inspect-file/viewers/tum-viewer.vue @@ -0,0 +1,288 @@ + + + + + diff --git a/frontend/src/components/inspect-file/viewers/twist-stamped-viewer.vue b/frontend/src/components/inspect-file/viewers/twist-stamped-viewer.vue index 971ab60cb..bd0351cc4 100644 --- a/frontend/src/components/inspect-file/viewers/twist-stamped-viewer.vue +++ b/frontend/src/components/inspect-file/viewers/twist-stamped-viewer.vue @@ -1,70 +1,42 @@ diff --git a/frontend/src/components/metadata-filter-input.vue b/frontend/src/components/metadata-filter-input.vue index 316072fe9..afb314643 100644 --- a/frontend/src/components/metadata-filter-input.vue +++ b/frontend/src/components/metadata-filter-input.vue @@ -42,18 +42,20 @@ diff --git a/frontend/src/components/select-mission-tags.vue b/frontend/src/components/select-mission-tags.vue index 37724d7ce..ab15e9779 100644 --- a/frontend/src/components/select-mission-tags.vue +++ b/frontend/src/components/select-mission-tags.vue @@ -142,8 +142,8 @@ diff --git a/frontend/src/composables/rosmsg-strategies.ts b/frontend/src/composables/rosmsg-strategies.ts deleted file mode 100644 index eafdaad57..000000000 --- a/frontend/src/composables/rosmsg-strategies.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { UniversalHttpReader } from '@common/universal-http-reader'; -import { Bag } from '@foxglove/rosbag'; -import { parse as parseMessageDefer } from '@foxglove/rosmsg'; -import { MessageReader as Ros1Reader } from '@foxglove/rosmsg-serialization'; -import { MessageReader as CdrReader } from '@foxglove/rosmsg2-serialization'; -import { McapIndexedReader } from '@mcap/core'; -import * as fzstd from 'fzstd'; -import lz4js from 'lz4js'; - -export interface LogMessage { - logTime: bigint; - data: any; -} - -export interface LogStrategy { - init(reader: UniversalHttpReader): Promise; - getMessages( - topic: string, - limit?: number, - onMessage?: (message: LogMessage) => void, - ): Promise; -} - -// --- MCAP Implementation --- -export class McapStrategy implements LogStrategy { - private reader: McapIndexedReader | null = null; - private decoders = new Map(); - - async init(httpReader: UniversalHttpReader): Promise { - this.reader = await McapIndexedReader.Initialize({ - readable: httpReader, - decompressHandlers: { - zstd: (buffer: Uint8Array) => fzstd.decompress(buffer), - lz4: (buffer: Uint8Array) => lz4js.decompress(buffer), - bz2: () => { - throw new Error('BZ2 not supported'); - }, - }, - }); - } - - async getMessages( - topic: string, - limit = 10, - onMessage?: (message: LogMessage) => void, - ): Promise { - if (!this.reader) return []; - const msgs: LogMessage[] = []; - - for await (const message of this.reader.readMessages({ - topics: [topic], - })) { - if (msgs.length >= limit) break; - let data = message.data; - const channel = this.reader.channelsById.get(message.channelId); - if (channel) { - data = - (await this.tryDecode(channel.schemaId, message.data)) ?? - message.data; - } - const messageObject = { logTime: message.logTime, data }; - if (onMessage) onMessage(messageObject); - msgs.push(messageObject); - } - return msgs; - } - - private tryDecode(schemaId: number, data: Uint8Array): any { - if (!this.reader) return; - let decoder = this.decoders.get(schemaId); - if (!decoder) { - const schema = this.reader.schemasById.get(schemaId); - if (!schema) return; - try { - const defs = parseMessageDefer( - new TextDecoder().decode(schema.data), - ); - if (schema.encoding.includes('ros1')) - decoder = new Ros1Reader(defs); - else if (['cdr', 'ros2msg'].includes(schema.encoding)) - decoder = new CdrReader(defs); - if (decoder) this.decoders.set(schemaId, decoder); - } catch { - return; - } - } - return decoder?.readMessage(data); - } -} - -// --- Rosbag Implementation --- -export class RosbagStrategy implements LogStrategy { - private bag: Bag | undefined = undefined; - - async init(httpReader: UniversalHttpReader): Promise { - this.bag = new Bag( - { - read: (offset, length): Promise => - httpReader.read(BigInt(offset), BigInt(length)), - size: (): number => httpReader.sizeBytes, - }, - { - decompress: { - zstd: (buffer): Uint8Array => fzstd.decompress(buffer), - lz4: (buffer): Uint8Array => lz4js.decompress(buffer), - }, - }, - ); - await this.bag.open(); - } - - async getMessages( - topic: string, - limit = 10, - onMessage?: (message: LogMessage) => void, - ): Promise { - if (!this.bag) return []; - const msgs: LogMessage[] = []; - const iterator = this.bag.messageIterator({ topics: [topic] }); - // eslint-disable-next-line unicorn/consistent-function-scoping - const toNano = (t: { sec: number; nsec: number }) => - BigInt(t.sec) * 1_000_000_000n + BigInt(t.nsec); - - for await (const result of iterator) { - if (msgs.length >= limit) break; - const messageObject = { - logTime: toNano(result.timestamp), - data: result.message, - }; - if (onMessage) onMessage(messageObject); - msgs.push(messageObject); - } - return msgs; - } -} diff --git a/frontend/src/composables/rosmsg-utilities.ts.ts b/frontend/src/composables/rosmsg-utilities.ts.ts index 0c07d6973..14d4532d8 100644 --- a/frontend/src/composables/rosmsg-utilities.ts.ts +++ b/frontend/src/composables/rosmsg-utilities.ts.ts @@ -1,15 +1,20 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function formatPayload(data: any): string { if (data === null || data === undefined) return '[Empty]'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const isBinary = data instanceof Uint8Array || data?.type === 'Buffer'; if (isBinary) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const array = data.data - ? new Uint8Array(data.data) + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + new Uint8Array(data.data) : new Uint8Array(data); const max = 12; const hex = [...array.subarray(0, max)] .map((b) => b.toString(16).padStart(2, '0').toUpperCase()) .join(' '); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions return `[Binary ${array.byteLength} bytes] <${hex}${array.byteLength > max ? '...' : ''}>`; } @@ -21,7 +26,9 @@ export function formatPayload(data: any): string { (_, v) => { // Collapse large arrays to avoid vertical spam if (Array.isArray(v) && v.length > 20) + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions return `[Array(${v.length})]`; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return v; }, 2, diff --git a/frontend/src/composables/use-action-mutations.ts b/frontend/src/composables/use-action-mutations.ts new file mode 100644 index 000000000..797ea03a8 --- /dev/null +++ b/frontend/src/composables/use-action-mutations.ts @@ -0,0 +1,124 @@ +import type { ActionTemplateDto } from '@kleinkram/api-dto/types/actions/action-template.dto'; +import type { CreateTemplateDto } from '@kleinkram/api-dto/types/actions/create-template.dto'; +import type { UpdateTemplateDto } from '@kleinkram/api-dto/types/actions/update-template.dto'; +import type { ActionSubmitResponseDto } from '@kleinkram/api-dto/types/submit-action-response.dto'; +import { + useMutation, + UseMutationReturnType, + useQueryClient, +} from '@tanstack/vue-query'; +import { actionKeys } from 'src/api/keys/action-keys'; +import { ActionService } from 'src/api/services/action.service'; + +export function useCreateTemplate(): UseMutationReturnType< + ActionTemplateDto, + Error, + CreateTemplateDto, + unknown +> { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (dto: CreateTemplateDto) => + ActionService.createTemplate(dto), + onSuccess: () => { + return queryClient.invalidateQueries({ + queryKey: actionKeys.templates.all, + }); + }, + }); +} + +export function useUpdateTemplateVersion(): UseMutationReturnType< + ActionTemplateDto, + Error, + UpdateTemplateDto, + unknown +> { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (dto: UpdateTemplateDto) => + ActionService.createTemplateVersion(dto), + onSuccess: async (data) => { + await Promise.all([ + queryClient.invalidateQueries({ + queryKey: actionKeys.templates.all, + }), + queryClient.invalidateQueries({ + queryKey: actionKeys.templates.revisions(data.uuid), + }), + ]); + }, + }); +} + +export function useDeleteAction(): UseMutationReturnType< + void, + Error, + string, + unknown +> { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (uuid: string) => ActionService.delete(uuid), + onSuccess: () => { + return queryClient.invalidateQueries({ queryKey: actionKeys.all }); + }, + }); +} + +export function useDeleteTemplate(): UseMutationReturnType< + void, + Error, + string, + unknown +> { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (uuid: string) => ActionService.deleteTemplate(uuid), + onSuccess: () => { + return queryClient.invalidateQueries({ + queryKey: actionKeys.templates.all, + }); + }, + }); +} + +export interface SubmitActionPayload { + missionUUID?: string | undefined; + missionUUIDs?: string[] | undefined; + templateUUID: string; +} + +export function useSubmitAction(): UseMutationReturnType< + ActionSubmitResponseDto[] | ActionSubmitResponseDto, + Error, + SubmitActionPayload, + unknown +> { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload: SubmitActionPayload) => { + if (payload.missionUUIDs && payload.missionUUIDs.length > 0) { + return ActionService.createMultipleAnalysis({ + missionUUIDs: payload.missionUUIDs, + templateUUID: payload.templateUUID, + }); + } else if (payload.missionUUID) { + return ActionService.createAnalysis({ + missionUUID: payload.missionUUID, + templateUUID: payload.templateUUID, + }); + } + throw new Error('No mission selected for execution'); + }, + onSuccess: () => { + return queryClient.invalidateQueries({ + queryKey: actionKeys.running(), + }); + }, + }); +} diff --git a/frontend/src/composables/use-actions-queries.ts b/frontend/src/composables/use-actions-queries.ts new file mode 100644 index 000000000..c4f0c2ee2 --- /dev/null +++ b/frontend/src/composables/use-actions-queries.ts @@ -0,0 +1,111 @@ +import type { ActionLogsDto } from '@kleinkram/api-dto/types/actions/action-logs.dto'; +import type { ActionTemplatesDto } from '@kleinkram/api-dto/types/actions/action-templates.dto'; +import type { ActionDto } from '@kleinkram/api-dto/types/actions/action.dto'; +import type { ActionsDto } from '@kleinkram/api-dto/types/actions/actions.dto'; +import type { FileEventsDto } from '@kleinkram/api-dto/types/file/file-event.dto'; +import type { ActionQuery } from '@kleinkram/api-dto/types/submit-action.dto'; +import { ActionState } from '@kleinkram/shared'; +import { useQuery, UseQueryReturnType } from '@tanstack/vue-query'; +import { actionKeys } from 'src/api/keys/action-keys'; +import { ActionService } from 'src/api/services/action.service'; +import { computed, ComputedRef, MaybeRef, unref } from 'vue'; + +export function useActionDetails( + uuid: MaybeRef, +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => actionKeys.detail(uuid)), + queryFn: ({ queryKey }) => { + const _uuid = queryKey[2]; + return ActionService.getOne(_uuid); + }, + enabled: computed(() => !!unref(uuid)), + refetchInterval: 5000, + }); +} + +export function useActionList( + filters: MaybeRef | ComputedRef, +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => actionKeys.list(unref(filters))), + + queryFn: ({ queryKey }) => { + const _filters = queryKey[2]; + return ActionService.getAll(_filters as ActionQuery); + }, + }); +} + +export function useTemplateRevisions( + uuid: MaybeRef, +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => actionKeys.templates.revisions(uuid)), + queryFn: () => ActionService.getTemplateRevisions(unref(uuid)), + enabled: computed(() => !!unref(uuid)), + }); +} + +export function useTemplateList( + parameters: MaybeRef<{ search?: string; includeArchived?: boolean }>, +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => actionKeys.templates.list(unref(parameters))), + queryFn: () => { + const p = unref(parameters); + return ActionService.listTemplates(p.search, p.includeArchived); + }, + }); +} + +export function useRunningActions(): UseQueryReturnType { + const filters = computed(() => ({ + skip: 0, + take: 10, + states: [ActionState.PROCESSING], + })); + + const { data, refetch, isLoading, error, isFetched } = + useActionList(filters); + + // We need to return the same structure as useQuery + return { + data, + refetch, + isLoading, + error, + isFetched, + } as UseQueryReturnType; +} + +export function useActionLogs( + uuid: MaybeRef, + skip: MaybeRef = 0, + take: MaybeRef = 1000, +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => [ + ...actionKeys.detail(unref(uuid)), + 'logs', + String(unref(skip)), + String(unref(take)), + ]), + queryFn: () => + ActionService.getLogs(unref(uuid), unref(skip), unref(take)), + enabled: computed(() => !!unref(uuid)), + }); +} + +export function useActionFileEvents( + uuid: MaybeRef, +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => [ + ...actionKeys.detail(unref(uuid)), + 'file-events', + ]), + queryFn: () => ActionService.getActionFileEvents(unref(uuid)), + enabled: computed(() => !!unref(uuid)), + }); +} diff --git a/frontend/src/composables/use-file-queue.ts b/frontend/src/composables/use-file-queue.ts new file mode 100644 index 000000000..18076ef3e --- /dev/null +++ b/frontend/src/composables/use-file-queue.ts @@ -0,0 +1,96 @@ +import type { + CancelProcessingResponseDto, + DeleteMissionResponseDto, + FileQueueEntriesDto, +} from '@kleinkram/api-dto'; +import { + useMutation, + UseMutationReturnType, + useQuery, + useQueryClient, + UseQueryReturnType, +} from '@tanstack/vue-query'; +import { fileKeys } from 'src/api/keys/file-keys'; +import { + cancelProcessing, + deleteQueueItem, + getQueue, + stopQueueItem, +} from 'src/services/mutations/file'; +import { computed, MaybeRef, unref } from 'vue'; + +export function useQueue( + startDate: MaybeRef, + stateFilter: MaybeRef, + pagination: MaybeRef<{ skip: number; take: number }>, +): UseQueryReturnType { + const queryKey = computed(() => + fileKeys.queue.list(startDate, stateFilter, pagination), + ); + const enabled = computed(() => !!unref(startDate)); + + return useQuery({ + queryKey, + queryFn: async () => + await getQueue( + unref(startDate), + unref(stateFilter), + unref(pagination), + ), + refetchInterval: 1000, + enabled, + }); +} + +export function useDeleteQueueItem(): UseMutationReturnType< + DeleteMissionResponseDto, + Error, + { missionUUID: string; queueUUID: string }, + unknown +> { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (params: { missionUUID: string; queueUUID: string }) => + deleteQueueItem(params.missionUUID, params.queueUUID), + onSuccess: () => { + return queryClient.invalidateQueries({ + queryKey: fileKeys.queue.all, + }); + }, + }); +} + +export function useCancelProcessing(): UseMutationReturnType< + CancelProcessingResponseDto, + Error, + { missionUUID: string; queueUUID: string }, + unknown +> { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (params: { missionUUID: string; queueUUID: string }) => + cancelProcessing(params.queueUUID, params.missionUUID), + onSuccess: () => { + return queryClient.invalidateQueries({ + queryKey: fileKeys.queue.all, + }); + }, + }); +} + +export function useStopQueueItem(): UseMutationReturnType< + { success: boolean }, + Error, + string, + unknown +> { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (queueUUID: string) => stopQueueItem(queueUUID), + onSuccess: () => { + return queryClient.invalidateQueries({ + queryKey: fileKeys.queue.all, + }); + }, + }); +} diff --git a/frontend/src/composables/use-image-decoder.ts b/frontend/src/composables/use-image-decoder.ts index cc28bbef0..3f76eef8d 100644 --- a/frontend/src/composables/use-image-decoder.ts +++ b/frontend/src/composables/use-image-decoder.ts @@ -1,22 +1,114 @@ import { ref, Ref, watch } from 'vue'; -import { renderMessageToCanvas } from '../services/image-utilities'; +import { renderMessageToCanvas } from '../services/decoding-strategies/image-utilities'; + +// Maximum number of attempts before aborting compressed image loading +const MAX_COMPRESSED_ATTEMPTS = 1000; +// Maximum parallel image load requests to avoid flooding +const MAX_PARALLEL_REQUESTS = 100; export function useImageDecoder( canvasReference: Ref, messageData: Ref, -): { draw: () => void; renderError: Ref } { +): { + draw: () => void; + renderError: Ref; + isDecoded: Ref; + isUltraWide: Ref; +} { const renderError = ref(undefined); - + const isDecoded = ref(false); + const isUltraWide = ref(false); + // Track total attempts for compressed images + const compressedAttempts = ref(0); + // Track currently pending image load requests + const parallelRequests = ref(0); const draw = (): void => { if (!canvasReference.value || !messageData.value) return; - try { - renderError.value = undefined; - renderMessageToCanvas(messageData.value, canvasReference.value); - } catch (error: unknown) { - console.error('Image render failed', error); - renderError.value = 'Failed to render frame'; + // Determine if this is a compressed image + const isCompressed = + 'format' in messageData.value || + (!('encoding' in messageData.value) && + !('width' in messageData.value) && + !('height' in messageData.value)); + + // Abort if too many attempts for compressed images + if ( + isCompressed && + compressedAttempts.value >= MAX_COMPRESSED_ATTEMPTS + ) { + renderError.value = 'Loading aborted after too many attempts'; + return; + } + // Abort if too many parallel requests are ongoing + if (isCompressed && parallelRequests.value >= MAX_PARALLEL_REQUESTS) { + renderError.value = 'Too many parallel image requests'; + return; } + + // Increment counters for compressed images + if (isCompressed) { + compressedAttempts.value++; + parallelRequests.value++; + } + + renderError.value = undefined; + const canvas = canvasReference.value; + const targetWidth = 600; + const targetHeight = 400; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + renderMessageToCanvas(messageData.value, canvas, () => { + // Callback after rendering (async for compressed images) + const aspectRatio = canvas.width / canvas.height; + isUltraWide.value = aspectRatio > 2.5; + + // Resize logic + let newWidth = canvas.width; + let newHeight = canvas.height; + let needsResize = false; + + if (isUltraWide.value) { + if (canvas.height > targetHeight) { + const ratio = targetHeight / canvas.height; + newWidth = Math.round(canvas.width * ratio); + newHeight = targetHeight; + needsResize = true; + } + } else { + if ( + canvas.width > targetWidth || + canvas.height > targetHeight + ) { + const ratio = Math.min( + targetWidth / canvas.width, + targetHeight / canvas.height, + ); + newWidth = Math.round(canvas.width * ratio); + newHeight = Math.round(canvas.height * ratio); + needsResize = true; + } + } + + if (needsResize) { + const temporaryCanvas = document.createElement('canvas'); + temporaryCanvas.width = canvas.width; + temporaryCanvas.height = canvas.height; + const temporaryContext = temporaryCanvas.getContext('2d'); + temporaryContext?.drawImage(canvas, 0, 0); + + canvas.width = newWidth; + canvas.height = newHeight; + const context = canvas.getContext('2d'); + context?.drawImage(temporaryCanvas, 0, 0, newWidth, newHeight); + } + + isDecoded.value = true; + if (isCompressed) { + // Decrement parallel request count after completion + parallelRequests.value--; + } + }); }; // Automatically redraw when data changes @@ -26,5 +118,7 @@ export function useImageDecoder( return { draw, renderError, + isDecoded, + isUltraWide, }; } diff --git a/frontend/src/composables/use-rosmsg-preview.ts b/frontend/src/composables/use-rosmsg-preview.ts index c79f637e5..0888c2600 100644 --- a/frontend/src/composables/use-rosmsg-preview.ts +++ b/frontend/src/composables/use-rosmsg-preview.ts @@ -1,30 +1,76 @@ -import { UniversalHttpReader } from '@common/universal-http-reader'; -import { reactive, Ref, ref, shallowRef } from 'vue'; -import { LogStrategy, McapStrategy, RosbagStrategy } from './rosmsg-strategies'; +import { UniversalHttpReader } from '@kleinkram/shared'; +import { markRaw, reactive, Ref, ref, shallowRef } from 'vue'; +import { DecodingStrategy } from '../services/decoding-strategies'; +import { Db3Strategy } from '../services/decoding-strategies/db3-strategy'; +import { McapStrategy } from '../services/decoding-strategies/mcap-strategy'; +import { RosbagStrategy } from '../services/decoding-strategies/rosbag-strategy'; import { formatPayload } from './rosmsg-utilities.ts'; export function useRosmsgPreview(): { isReaderReady: Ref; readerError: Ref; + // eslint-disable-next-line @typescript-eslint/no-explicit-any topicPreviews: Record; topicLoadingState: Record; topicErrors: Record; - init: (url: string, type: 'mcap' | 'rosbag') => Promise; - fetchTopicMessages: (topicName: string, limit?: number) => Promise; + init: ( + url: string, + type: 'mcap' | 'rosbag' | 'db3', + missionUuid?: string, + ) => Promise; + fetchTopicMessages: ( + topicName: string, + options?: { limit?: number; append?: boolean }, + ) => Promise; + // eslint-disable-next-line @typescript-eslint/no-explicit-any formatPayload: (data: any) => string; + cancelTopic: (topicName: string) => void; + reset: () => void; + dbSchema?: Ref; } { const isReaderReady = ref(false); const readerError = ref(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const topicPreviews = reactive>({}); const topicLoadingState = reactive>({}); const topicErrors = reactive>({}); - const strategy = shallowRef(null); + const strategy = shallowRef(null); + const dbSchema = ref(null); + const abortControllers = new Map(); + + function cancelTopic(topicName: string): void { + const controller = abortControllers.get(topicName); + if (controller) { + controller.abort(); + abortControllers.delete(topicName); + topicLoadingState[topicName] = false; + } + } + + function reset(): void { + dbSchema.value = null; + for (const controller of abortControllers.values()) { + controller.abort(); + } + abortControllers.clear(); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + for (const k of Object.keys(topicPreviews)) delete topicPreviews[k]; + + for (const k of Object.keys(topicLoadingState)) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete topicLoadingState[k]; + } + } - async function init(url: string, type: 'mcap' | 'rosbag'): Promise { + async function init( + url: string, + type: 'mcap' | 'rosbag' | 'db3', + ): Promise { isReaderReady.value = false; readerError.value = null; + reset(); // Clear previous errors on new file load // eslint-disable-next-line @typescript-eslint/no-dynamic-delete for (const k of Object.keys(topicErrors)) delete topicErrors[k]; @@ -33,14 +79,24 @@ export function useRosmsgPreview(): { const httpReader = new UniversalHttpReader(url); await httpReader.init(); - const impl = - type === 'mcap' ? new McapStrategy() : new RosbagStrategy(); - await impl.init(httpReader); + let impl: DecodingStrategy; + if (type === 'mcap') impl = new McapStrategy(); + else if (type === 'db3') { + impl = new Db3Strategy(); + await impl.init(httpReader); + dbSchema.value = impl.getSchema(); + } else impl = new RosbagStrategy(); + + if (type !== 'db3') { + await impl.init(httpReader); + } strategy.value = impl; isReaderReady.value = true; + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { console.error('Preview init failed:', error); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access readerError.value = error.message; } } @@ -51,33 +107,68 @@ export function useRosmsgPreview(): { */ async function fetchTopicMessages( topicName: string, - limit?: number, + options?: { limit?: number; append?: boolean }, ): Promise { if (!strategy.value) return; + // Cancel any existing request for this topic + cancelTopic(topicName); + + const controller = new AbortController(); + abortControllers.set(topicName, controller); + topicLoadingState[topicName] = true; topicErrors[topicName] = null; - // Reset the array so it can be filled from scratch - // If you want append logic (load more), you would check array length here - // But typically we are reloading or loading a fresh batch. - topicPreviews[topicName] = []; + const limit = options?.limit ?? 10; + const append = options?.append ?? false; + + let startTime: bigint | undefined; + + if (append) { + // Calculate start time from the last message + const currentMessages = topicPreviews[topicName]; + if (currentMessages && currentMessages.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const lastMessage = currentMessages.at(-1); + // Add 1ns to avoid duplicate of the last message + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + startTime = BigInt(lastMessage.logTime) + 1n; + } + } else { + // Reset the array so it can be filled from scratch + topicPreviews[topicName] = []; + } try { // We ignore the return value (full array) because we populate // the reactive array via the callback for immediate UI feedback. - await strategy.value.getMessages(topicName, limit, (message) => { - (topicPreviews[topicName] ??= []).push(message); - }); + await strategy.value.getMessages( + topicName, + limit, + (message) => { + if (controller.signal.aborted) return; + // Use markRaw to prevent deep reactivity overhead + (topicPreviews[topicName] ??= []).push(markRaw(message)); + }, + controller.signal, + startTime, + ); } catch (error: unknown) { + if (controller.signal.aborted) return; // Ignore abort errors + console.error(`Error reading ${topicName}`, error); // Don't necessarily clear previews on error, we might have partial data + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (!topicPreviews[topicName]) topicPreviews[topicName] = []; topicErrors[topicName] = error instanceof Error ? error.message : String(error); } finally { - topicLoadingState[topicName] = false; + if (!controller.signal.aborted) { + topicLoadingState[topicName] = false; + abortControllers.delete(topicName); + } } } @@ -87,8 +178,11 @@ export function useRosmsgPreview(): { topicPreviews, topicLoadingState, topicErrors, + dbSchema, init, fetchTopicMessages, formatPayload, + cancelTopic, + reset, }; } diff --git a/frontend/src/composables/use-scope-selection.ts b/frontend/src/composables/use-scope-selection.ts new file mode 100644 index 000000000..6f164ea88 --- /dev/null +++ b/frontend/src/composables/use-scope-selection.ts @@ -0,0 +1,125 @@ +import type { FlatMissionDto } from '@kleinkram/api-dto/types/mission/mission.dto'; +import type { ProjectWithRequiredTagsDto } from '@kleinkram/api-dto/types/project/project-with-required-tags.dto'; +import { + useFilteredProjects, + useHandler, + useMissionsOfProjectMinimal, +} from 'src/hooks/query-hooks'; +import { computed, ComputedRef, Ref } from 'vue'; + +export function useScopeSelection( + localProjectUuid?: Ref, + localMissionUuid?: Ref, + customProjects?: Ref, +): { + // Data + projects: ComputedRef; + missions: ComputedRef; + + // Selection State + selectedProjectUuid: ComputedRef; + selectedMissionUuid: ComputedRef; + selectedProject: ComputedRef; + selectedMission: ComputedRef; + + // Loading States + isLoading: ComputedRef; + isProjectsLoading: + | Ref + | Ref + | Ref; + isMissionsLoading: + | Ref + | Ref + | Ref; + + // Actions + setProject: (uuid: string | undefined) => void; + setMission: (uuid: string | undefined) => void; +} { + const handler = useHandler(); + const isLocalMode = !!localProjectUuid; + + const { data: projectsData, isLoading: isProjectsLoading } = + useFilteredProjects(500, 0, 'name', false); + + const projects = computed(() => { + if (customProjects?.value) return customProjects.value; + return projectsData.value?.data ?? []; + }); + + // If local ref exists, use it. Otherwise, read from Handler. + const selectedProjectUuid = computed(() => + isLocalMode ? localProjectUuid.value : handler.value.projectUuid, + ); + + const selectedMissionUuid = computed(() => + isLocalMode ? localMissionUuid?.value : handler.value.missionUuid, + ); + + const selectedProject = computed(() => { + return projects.value.find((p) => p.uuid === selectedProjectUuid.value); + }); + + const selectedMission = computed(() => { + return missions.value.find( + (m: FlatMissionDto) => m.uuid === selectedMissionUuid.value, + ); + }); + + // Missions (Reactive to whichever Project is selected) --- + const { data: missionsData, isLoading: isMissionsLoading } = + useMissionsOfProjectMinimal( + computed(() => selectedProjectUuid.value ?? ''), + 500, + 0, + ); + + const missions = computed(() => missionsData.value?.data ?? []); + + const setProject = (uuid: string | undefined | null): void => { + const value = uuid ?? undefined; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (isLocalMode && localProjectUuid) { + localProjectUuid.value = value; + if (localMissionUuid) localMissionUuid.value = undefined; + } else { + handler.value.setProjectUUID(value); + if (handler.value.missionUuid) + handler.value.setMissionUUID(undefined); + } + }; + + const setMission = (uuid: string | undefined | null): void => { + const value = uuid ?? undefined; + if (isLocalMode && localMissionUuid) { + localMissionUuid.value = value; + } else { + handler.value.setMissionUUID(value); + } + }; + + return { + // Data + projects, + missions, + + // State (UUIDs) + selectedProjectUuid, + selectedMissionUuid, + selectedProject, + selectedMission, + + // Actions + setProject, + setMission, + + // Loading + isLoading: computed( + () => isProjectsLoading.value || isMissionsLoading.value, + ), + isProjectsLoading, + isMissionsLoading, + }; +} diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts new file mode 100644 index 000000000..dedaedf4a --- /dev/null +++ b/frontend/src/constants.ts @@ -0,0 +1 @@ +export const DEFAULT_PAGINATION_SIZE = 20; diff --git a/frontend/src/css/app.scss b/frontend/src/css/app.scss index b526ec012..f2ca3ecd0 100644 --- a/frontend/src/css/app.scss +++ b/frontend/src/css/app.scss @@ -192,3 +192,14 @@ label { .q-notification__actions { color: white; } + +// Unified disabled/readonly input style +.q-field--readonly, +.q-field--disabled { + .q-field__control:before { + border-style: dotted !important; + } + .q-field__control { + background-color: $layer-01 !important; + } +} diff --git a/frontend/src/dialogs/add-multi-category.vue b/frontend/src/dialogs/add-multi-category.vue index f247f782a..19c683eeb 100644 --- a/frontend/src/dialogs/add-multi-category.vue +++ b/frontend/src/dialogs/add-multi-category.vue @@ -43,14 +43,14 @@ diff --git a/frontend/src/dialogs/add-user-access-group-dialog.vue b/frontend/src/dialogs/add-user-access-group-dialog.vue index 66649da11..2464aad2b 100644 --- a/frontend/src/dialogs/add-user-access-group-dialog.vue +++ b/frontend/src/dialogs/add-user-access-group-dialog.vue @@ -28,7 +28,7 @@ import { ref } from 'vue'; const { dialogRef, onDialogOK } = useDialogPluginComponent(); const addUserReference = ref | null>( // TODO: check why we need null as a value - // eslint-disable-next-line unicorn/no-null + null, ); @@ -37,7 +37,9 @@ const { accessGroupUuid } = defineProps<{ }>(); const addUserToAccessGroupAction = (): void => { - addUserReference.value?.mutate(); + if (addUserReference.value) { + addUserReference.value.mutate?.(); + } onDialogOK(); }; diff --git a/frontend/src/dialogs/base-dialog.vue b/frontend/src/dialogs/base-dialog.vue index 8339e75f3..172bd18a1 100644 --- a/frontend/src/dialogs/base-dialog.vue +++ b/frontend/src/dialogs/base-dialog.vue @@ -65,7 +65,9 @@ export default { contentStyle: ComputedRef; } { const contentStyle = computed(() => ({ - height: properties.contentHeight || 'auto', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + height: properties.contentHeight ?? 'auto', + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access overflowY: properties.contentHeight ? 'hidden' : 'visible', })); const dialogPlugin = useDialogPluginComponent(); diff --git a/frontend/src/dialogs/confirm-delete-dialog.vue b/frontend/src/dialogs/confirm-delete-dialog.vue index 8a8821c8f..8e6450afd 100644 --- a/frontend/src/dialogs/confirm-delete-dialog.vue +++ b/frontend/src/dialogs/confirm-delete-dialog.vue @@ -21,6 +21,17 @@ {{ filenames.slice(-1)[0] }} +

+ Please confirm by entering: + delete {{ filenames.length }} files +

+
@@ -29,6 +40,9 @@ import { useDialogPluginComponent } from 'quasar'; import BaseDialog from 'src/dialogs/base-dialog.vue'; +import { ref } from 'vue'; const { dialogRef, onDialogOK, onDialogCancel } = useDialogPluginComponent(); const { filenames } = defineProps<{ filenames: string[]; }>(); + +const confirmationInput = ref(''); diff --git a/frontend/src/dialogs/create-file-dialog.vue b/frontend/src/dialogs/create-file-dialog.vue index cb52209e3..0dce7aece 100644 --- a/frontend/src/dialogs/create-file-dialog.vue +++ b/frontend/src/dialogs/create-file-dialog.vue @@ -8,6 +8,7 @@ v-model:ready="ready" :mission="mission" :uploads="uploads" + :disable-scope="!!mission" /> @@ -24,8 +25,8 @@ diff --git a/frontend/src/dialogs/create-mission-dialog.vue b/frontend/src/dialogs/create-mission-dialog.vue index 82b8cdc95..766491a39 100644 --- a/frontend/src/dialogs/create-mission-dialog.vue +++ b/frontend/src/dialogs/create-mission-dialog.vue @@ -50,33 +50,15 @@ projects exist yet.
- - - - - - - {{ _project.name }} - - - - - + - diff --git a/frontend/src/dialogs/delete-action-template-dialog.vue b/frontend/src/dialogs/delete-action-template-dialog.vue new file mode 100644 index 000000000..0c9ed94e2 --- /dev/null +++ b/frontend/src/dialogs/delete-action-template-dialog.vue @@ -0,0 +1,59 @@ + + + diff --git a/frontend/src/dialogs/delete-file-dialog.vue b/frontend/src/dialogs/delete-file-dialog.vue index e3af36a53..ad2f8006e 100644 --- a/frontend/src/dialogs/delete-file-dialog.vue +++ b/frontend/src/dialogs/delete-file-dialog.vue @@ -24,7 +24,7 @@ import { useDialogPluginComponent } from 'quasar'; import BaseDialog from 'src/dialogs/base-dialog.vue'; import { ref } from 'vue'; -import { FileWithTopicDto } from '@api/types/file/file.dto'; +import type { FileWithTopicDto } from '@kleinkram/api-dto/types/file/file.dto'; import DeleteFile from 'components/delete-file.vue'; const { dialogRef, onDialogOK } = useDialogPluginComponent(); @@ -38,7 +38,11 @@ const { file } = defineProps<{ const deleteFileAction = (): void => { if (deleteFileReference.value === undefined) return; - deleteFileReference.value.deleteFileAction(); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (deleteFileReference.value) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + (deleteFileReference.value as any).deleteFileAction(); + } onDialogOK(); }; diff --git a/frontend/src/dialogs/delete-mission-dialog.vue b/frontend/src/dialogs/delete-mission-dialog.vue index be1db3833..087d8ffb9 100644 --- a/frontend/src/dialogs/delete-mission-dialog.vue +++ b/frontend/src/dialogs/delete-mission-dialog.vue @@ -43,7 +43,11 @@ const { data: mission } = useMission(computed(() => missionUuid)); const deleteMissionAction = (): void => { if (deleteMissionReference.value === undefined) return; - deleteMissionReference.value.deleteMissionAction(); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (deleteMissionReference.value) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + (deleteMissionReference.value as any).deleteMissionAction(); + } onDialogOK(); }; diff --git a/frontend/src/dialogs/modify-file-location-dialog.vue b/frontend/src/dialogs/modify-file-location-dialog.vue index f59d34796..695913795 100644 --- a/frontend/src/dialogs/modify-file-location-dialog.vue +++ b/frontend/src/dialogs/modify-file-location-dialog.vue @@ -6,73 +6,12 @@ + diff --git a/frontend/src/dialogs/modify-membership-expiration-date-dialog.vue b/frontend/src/dialogs/modify-membership-expiration-date-dialog.vue index 11dfe6591..fac1ab0b0 100644 --- a/frontend/src/dialogs/modify-membership-expiration-date-dialog.vue +++ b/frontend/src/dialogs/modify-membership-expiration-date-dialog.vue @@ -34,7 +34,7 @@ diff --git a/frontend/src/dialogs/modify-project-tags-dialog.vue b/frontend/src/dialogs/modify-project-tags-dialog.vue index e98a068b9..c452349fa 100644 --- a/frontend/src/dialogs/modify-project-tags-dialog.vue +++ b/frontend/src/dialogs/modify-project-tags-dialog.vue @@ -29,7 +29,7 @@ diff --git a/frontend/src/layouts/main-layout/header/bread-crumb-navigation.vue b/frontend/src/layouts/main-layout/header/bread-crumb-navigation.vue index 1f01d23e7..f1d7221a9 100644 --- a/frontend/src/layouts/main-layout/header/bread-crumb-navigation.vue +++ b/frontend/src/layouts/main-layout/header/bread-crumb-navigation.vue @@ -6,12 +6,16 @@ class="height-xl flex column justify-center q-px-lg" style="overflow-x: auto" > - + diff --git a/frontend/src/pages/action-page.vue b/frontend/src/pages/action-page.vue index a00a1288d..3e6c7a653 100644 --- a/frontend/src/pages/action-page.vue +++ b/frontend/src/pages/action-page.vue @@ -1,256 +1,197 @@